From 7916322fad9d11ef3fa7ca67574062bb6de3e950 Mon Sep 17 00:00:00 2001 From: You Liang Lim Date: Mon, 8 Aug 2016 17:32:17 +0800 Subject: [PATCH] address book level 4 draft --- .gitignore | 16 + VersionData.json | 92 ++++ build.gradle | 359 ++++++++++++++++ .../checkstyle/checkstyle-noframes-sorted.xsl | 195 +++++++++ config/checkstyle/checkstyle.xml | 326 +++++++++++++++ config/findbugs/excludeFilter.xml | 12 + copyright.txt | 9 + data/addressbook.xml | 57 +++ docs/addressbook/CloudSimulation.md | 78 ++++ docs/addressbook/Configuration.md | 46 ++ docs/addressbook/KeyboardShortcuts.md | 22 + docs/addressbook/Logging.md | 56 +++ docs/images/Release Cycle.jpg | Bin 0 -> 46942 bytes .../releases/Automatic Update Architecture.md | 40 ++ docs/releases/Launcher.md | 2 + docs/releases/Release.md | 163 ++++++++ docs/releases/Updater.md | 10 + docs/testing/Testing.md | 102 +++++ gradle.properties | 3 + gradle/wrapper/gradle-wrapper.jar | Bin 0 -> 52266 bytes gradle/wrapper/gradle-wrapper.properties | 6 + gradlew | 164 ++++++++ gradlew.bat | 90 ++++ src/main/java/address/MainApp.java | 109 +++++ .../java/address/browser/BrowserManager.java | 88 ++++ .../address/controller/HelpController.java | 28 ++ .../address/controller/MainController.java | 385 +++++++++++++++++ .../controller/PersonCardController.java | 113 +++++ .../controller/PersonListPanelController.java | 159 +++++++ .../controller/RootLayoutController.java | 106 +++++ .../controller/StatusBarFooterController.java | 60 +++ .../controller/StatusBarHeaderController.java | 29 ++ .../address/controller/TagCardController.java | 52 +++ .../address/controller/TagListController.java | 66 +++ .../java/address/controller/UiController.java | 21 + src/main/java/address/events/BaseEvent.java | 31 ++ .../java/address/events/EventManager.java | 63 +++ .../controller/JumpToListRequestEvent.java | 30 ++ .../controller/MinimizeAppRequestEvent.java | 13 + .../controller/ResizeAppRequestEvent.java | 13 + .../hotkey/AcceleratorIgnoredEvent.java | 25 ++ .../events/hotkey/GlobalHotkeyEvent.java | 25 ++ .../events/hotkey/KeyBindingEvent.java | 52 +++ .../events/model/LocalModelChangedEvent.java | 19 + .../events/parser/FilterCommittedEvent.java | 21 + .../storage/FileOpeningExceptionEvent.java | 24 ++ .../storage/FileSavingExceptionEvent.java | 24 ++ .../events/storage/LoadDataRequestEvent.java | 23 + .../events/storage/SaveDataRequestEvent.java | 27 ++ .../events/storage/SavePrefsRequestEvent.java | 21 + .../exceptions/DataConversionException.java | 14 + .../exceptions/DependencyCheckException.java | 14 + .../exceptions/DuplicateDataException.java | 19 + .../exceptions/DuplicatePersonException.java | 17 + .../exceptions/DuplicateTagException.java | 17 + .../java/address/main/ComponentManager.java | 37 ++ src/main/java/address/model/ModelManager.java | 194 +++++++++ src/main/java/address/model/UserPrefs.java | 19 + .../address/model/datatypes/AddressBook.java | 151 +++++++ .../datatypes/ExtractableObservables.java | 35 ++ .../model/datatypes/ReadOnlyAddressBook.java | 40 ++ .../address/model/datatypes/UniqueData.java | 65 +++ .../model/datatypes/person/Person.java | 337 +++++++++++++++ .../datatypes/person/ReadOnlyPerson.java | 244 +++++++++++ .../model/datatypes/tag/SelectableTag.java | 34 ++ .../java/address/model/datatypes/tag/Tag.java | 73 ++++ src/main/java/address/parser/AddCommand.java | 18 + src/main/java/address/parser/Command.java | 12 + .../java/address/parser/CommandParser.java | 32 ++ .../java/address/parser/DeleteCommand.java | 17 + .../java/address/parser/ParseException.java | 7 + src/main/java/address/parser/Parser.java | 91 ++++ .../java/address/parser/expr/AndExpr.java | 24 ++ src/main/java/address/parser/expr/Expr.java | 8 + .../java/address/parser/expr/NotExpr.java | 21 + .../java/address/parser/expr/PredExpr.java | 25 ++ .../parser/qualifier/CityQualifier.java | 22 + .../parser/qualifier/FirstNameQualifier.java | 22 + .../address/parser/qualifier/IdQualifier.java | 21 + .../parser/qualifier/LastNameQualifier.java | 22 + .../parser/qualifier/NameQualifier.java | 24 ++ .../address/parser/qualifier/Qualifier.java | 8 + .../parser/qualifier/StreetQualifier.java | 22 + .../parser/qualifier/TagQualifier.java | 25 ++ .../parser/qualifier/TrueQualifier.java | 19 + .../address/storage/StorageAddressBook.java | 53 +++ .../java/address/storage/StorageManager.java | 246 +++++++++++ .../java/address/storage/StoragePerson.java | 115 +++++ .../java/address/storage/XmlFileStorage.java | 38 ++ .../java/address/ui/PersonListViewCell.java | 24 ++ src/main/java/address/ui/Ui.java | 34 ++ src/main/java/address/util/AppLogger.java | 56 +++ src/main/java/address/util/Config.java | 80 ++++ .../java/address/util/DependencyChecker.java | 139 +++++++ src/main/java/address/util/GuiSettings.java | 41 ++ src/main/java/address/util/JavaVersion.java | 82 ++++ src/main/java/address/util/LoggerManager.java | 81 ++++ .../java/address/util/ManifestFileReader.java | 73 ++++ .../util/collections/FilteredList.java | 133 ++++++ .../UnmodifiableObservableList.java | 325 +++++++++++++++ src/main/java/commons/DateTimeUtil.java | 71 ++++ src/main/java/commons/FileUtil.java | 229 ++++++++++ src/main/java/commons/FxViewUtil.java | 17 + src/main/java/commons/JsonUtil.java | 99 +++++ src/main/java/commons/LibraryDescriptor.java | 47 +++ src/main/java/commons/OsDetector.java | 111 +++++ src/main/java/commons/PlatformExecUtil.java | 107 +++++ src/main/java/commons/StringUtil.java | 10 + src/main/java/commons/UrlUtil.java | 21 + src/main/java/commons/Version.java | 102 +++++ src/main/java/commons/VersionData.java | 35 ++ src/main/java/commons/XmlUtil.java | 107 +++++ src/main/java/launcher/Launcher.java | 67 +++ .../resources/help_html/css/bootstrap.min.css | 7 + src/main/resources/help_html/css/styles.css | 69 +++ src/main/resources/help_html/index.html | 182 ++++++++ .../resources/help_html/js/bootstrap.min.js | 6 + src/main/resources/help_html/js/scripts.js | 31 ++ src/main/resources/images/address_book_32.png | Bin 0 -> 4214 bytes src/main/resources/images/calendar.png | Bin 0 -> 1215 bytes src/main/resources/images/clock.png | Bin 0 -> 13751 bytes src/main/resources/images/fail.png | Bin 0 -> 20783 bytes src/main/resources/images/help_icon.png | Bin 0 -> 31256 bytes src/main/resources/images/info_icon.png | Bin 0 -> 5424 bytes src/main/resources/log4j2.json | 72 ++++ src/main/resources/view/ActivityHistory.fxml | 13 + .../resources/view/ActivityHistoryCard.fxml | 9 + src/main/resources/view/DarkTheme.css | 272 ++++++++++++ .../view/DefaultBrowserPlaceHolderScreen.fxml | 21 + src/main/resources/view/Extensions.css | 16 + src/main/resources/view/Help.fxml | 8 + src/main/resources/view/PersonListCard.fxml | 42 ++ src/main/resources/view/PersonListPanel.fxml | 14 + src/main/resources/view/RootLayout.fxml | 43 ++ src/main/resources/view/StatusBarFooter.fxml | 13 + src/main/resources/view/TagList.fxml | 7 + src/main/resources/view/TagListCard.fxml | 16 + src/test/data/XmlUtilTest/empty.xml | 0 src/test/data/XmlUtilTest/tempAddressBook.xml | 15 + .../data/XmlUtilTest/validAddressBook.xml | 24 ++ src/test/java/address/TestApp.java | 77 ++++ src/test/java/address/parser/ParserTest.java | 131 ++++++ .../address/storage/StorageManagerTest.java | 153 +++++++ .../address/testutil/AddressBookBuilder.java | 38 ++ .../address/testutil/BaseEventSubscriber.java | 12 + .../java/address/testutil/PersonBuilder.java | 82 ++++ .../java/address/testutil/ScreenShotRule.java | 17 + .../testutil/SerializableTestClass.java | 73 ++++ .../address/testutil/TestDataGenerator.java | 68 +++ src/test/java/address/testutil/TestUtil.java | 350 ++++++++++++++++ .../address/testutil/TypicalTestData.java | 60 +++ .../java/address/util/FilteredListTest.java | 122 ++++++ .../java/address/util/JavaVersionTest.java | 159 +++++++ .../util/UnmodifiableObservableListTest.java | 80 ++++ src/test/java/address/util/VersionTest.java | 138 ++++++ src/test/java/commons/FileUtilTest.java | 31 ++ src/test/java/commons/JsonUtilTest.java | 100 +++++ src/test/java/commons/UrlUtilTest.java | 43 ++ src/test/java/commons/XmlUtilTest.java | 93 +++++ .../java/guitests/FilterPersonsGuiTest.java | 142 +++++++ src/test/java/guitests/FullSystemTest.java | 12 + src/test/java/guitests/GuiRobot.java | 44 ++ src/test/java/guitests/GuiTestBase.java | 118 ++++++ .../java/guitests/guihandles/GuiHandle.java | 162 ++++++++ .../guihandles/HeaderStatusBarHandle.java | 25 ++ .../guitests/guihandles/MainGuiHandle.java | 57 +++ .../guitests/guihandles/MainMenuHandle.java | 21 + .../guihandles/ManageTagsDialogHandle.java | 43 ++ .../guitests/guihandles/PersonCardHandle.java | 134 ++++++ .../guihandles/PersonListPanelHandle.java | 392 ++++++++++++++++++ .../java/guiunittests/GuiUnitTestBase.java | 56 +++ 171 files changed, 11681 insertions(+) create mode 100644 .gitignore create mode 100644 VersionData.json create mode 100644 build.gradle create mode 100644 config/checkstyle/checkstyle-noframes-sorted.xsl create mode 100644 config/checkstyle/checkstyle.xml create mode 100644 config/findbugs/excludeFilter.xml create mode 100644 copyright.txt create mode 100644 data/addressbook.xml create mode 100644 docs/addressbook/CloudSimulation.md create mode 100644 docs/addressbook/Configuration.md create mode 100644 docs/addressbook/KeyboardShortcuts.md create mode 100644 docs/addressbook/Logging.md create mode 100644 docs/images/Release Cycle.jpg create mode 100644 docs/releases/Automatic Update Architecture.md create mode 100644 docs/releases/Launcher.md create mode 100644 docs/releases/Release.md create mode 100644 docs/releases/Updater.md create mode 100644 docs/testing/Testing.md create mode 100644 gradle.properties create mode 100644 gradle/wrapper/gradle-wrapper.jar create mode 100644 gradle/wrapper/gradle-wrapper.properties create mode 100755 gradlew create mode 100644 gradlew.bat create mode 100644 src/main/java/address/MainApp.java create mode 100644 src/main/java/address/browser/BrowserManager.java create mode 100644 src/main/java/address/controller/HelpController.java create mode 100644 src/main/java/address/controller/MainController.java create mode 100644 src/main/java/address/controller/PersonCardController.java create mode 100644 src/main/java/address/controller/PersonListPanelController.java create mode 100644 src/main/java/address/controller/RootLayoutController.java create mode 100644 src/main/java/address/controller/StatusBarFooterController.java create mode 100644 src/main/java/address/controller/StatusBarHeaderController.java create mode 100644 src/main/java/address/controller/TagCardController.java create mode 100644 src/main/java/address/controller/TagListController.java create mode 100644 src/main/java/address/controller/UiController.java create mode 100644 src/main/java/address/events/BaseEvent.java create mode 100644 src/main/java/address/events/EventManager.java create mode 100644 src/main/java/address/events/controller/JumpToListRequestEvent.java create mode 100644 src/main/java/address/events/controller/MinimizeAppRequestEvent.java create mode 100644 src/main/java/address/events/controller/ResizeAppRequestEvent.java create mode 100644 src/main/java/address/events/hotkey/AcceleratorIgnoredEvent.java create mode 100644 src/main/java/address/events/hotkey/GlobalHotkeyEvent.java create mode 100644 src/main/java/address/events/hotkey/KeyBindingEvent.java create mode 100644 src/main/java/address/events/model/LocalModelChangedEvent.java create mode 100644 src/main/java/address/events/parser/FilterCommittedEvent.java create mode 100644 src/main/java/address/events/storage/FileOpeningExceptionEvent.java create mode 100644 src/main/java/address/events/storage/FileSavingExceptionEvent.java create mode 100644 src/main/java/address/events/storage/LoadDataRequestEvent.java create mode 100644 src/main/java/address/events/storage/SaveDataRequestEvent.java create mode 100644 src/main/java/address/events/storage/SavePrefsRequestEvent.java create mode 100644 src/main/java/address/exceptions/DataConversionException.java create mode 100644 src/main/java/address/exceptions/DependencyCheckException.java create mode 100644 src/main/java/address/exceptions/DuplicateDataException.java create mode 100644 src/main/java/address/exceptions/DuplicatePersonException.java create mode 100644 src/main/java/address/exceptions/DuplicateTagException.java create mode 100644 src/main/java/address/main/ComponentManager.java create mode 100644 src/main/java/address/model/ModelManager.java create mode 100644 src/main/java/address/model/UserPrefs.java create mode 100644 src/main/java/address/model/datatypes/AddressBook.java create mode 100644 src/main/java/address/model/datatypes/ExtractableObservables.java create mode 100644 src/main/java/address/model/datatypes/ReadOnlyAddressBook.java create mode 100644 src/main/java/address/model/datatypes/UniqueData.java create mode 100644 src/main/java/address/model/datatypes/person/Person.java create mode 100644 src/main/java/address/model/datatypes/person/ReadOnlyPerson.java create mode 100644 src/main/java/address/model/datatypes/tag/SelectableTag.java create mode 100644 src/main/java/address/model/datatypes/tag/Tag.java create mode 100644 src/main/java/address/parser/AddCommand.java create mode 100644 src/main/java/address/parser/Command.java create mode 100644 src/main/java/address/parser/CommandParser.java create mode 100644 src/main/java/address/parser/DeleteCommand.java create mode 100644 src/main/java/address/parser/ParseException.java create mode 100644 src/main/java/address/parser/Parser.java create mode 100644 src/main/java/address/parser/expr/AndExpr.java create mode 100644 src/main/java/address/parser/expr/Expr.java create mode 100644 src/main/java/address/parser/expr/NotExpr.java create mode 100644 src/main/java/address/parser/expr/PredExpr.java create mode 100644 src/main/java/address/parser/qualifier/CityQualifier.java create mode 100644 src/main/java/address/parser/qualifier/FirstNameQualifier.java create mode 100644 src/main/java/address/parser/qualifier/IdQualifier.java create mode 100644 src/main/java/address/parser/qualifier/LastNameQualifier.java create mode 100644 src/main/java/address/parser/qualifier/NameQualifier.java create mode 100644 src/main/java/address/parser/qualifier/Qualifier.java create mode 100644 src/main/java/address/parser/qualifier/StreetQualifier.java create mode 100644 src/main/java/address/parser/qualifier/TagQualifier.java create mode 100644 src/main/java/address/parser/qualifier/TrueQualifier.java create mode 100644 src/main/java/address/storage/StorageAddressBook.java create mode 100644 src/main/java/address/storage/StorageManager.java create mode 100644 src/main/java/address/storage/StoragePerson.java create mode 100644 src/main/java/address/storage/XmlFileStorage.java create mode 100644 src/main/java/address/ui/PersonListViewCell.java create mode 100644 src/main/java/address/ui/Ui.java create mode 100644 src/main/java/address/util/AppLogger.java create mode 100644 src/main/java/address/util/Config.java create mode 100644 src/main/java/address/util/DependencyChecker.java create mode 100644 src/main/java/address/util/GuiSettings.java create mode 100644 src/main/java/address/util/JavaVersion.java create mode 100644 src/main/java/address/util/LoggerManager.java create mode 100644 src/main/java/address/util/ManifestFileReader.java create mode 100644 src/main/java/address/util/collections/FilteredList.java create mode 100644 src/main/java/address/util/collections/UnmodifiableObservableList.java create mode 100644 src/main/java/commons/DateTimeUtil.java create mode 100644 src/main/java/commons/FileUtil.java create mode 100644 src/main/java/commons/FxViewUtil.java create mode 100644 src/main/java/commons/JsonUtil.java create mode 100644 src/main/java/commons/LibraryDescriptor.java create mode 100644 src/main/java/commons/OsDetector.java create mode 100644 src/main/java/commons/PlatformExecUtil.java create mode 100644 src/main/java/commons/StringUtil.java create mode 100644 src/main/java/commons/UrlUtil.java create mode 100644 src/main/java/commons/Version.java create mode 100644 src/main/java/commons/VersionData.java create mode 100644 src/main/java/commons/XmlUtil.java create mode 100644 src/main/java/launcher/Launcher.java create mode 100644 src/main/resources/help_html/css/bootstrap.min.css create mode 100644 src/main/resources/help_html/css/styles.css create mode 100644 src/main/resources/help_html/index.html create mode 100644 src/main/resources/help_html/js/bootstrap.min.js create mode 100644 src/main/resources/help_html/js/scripts.js create mode 100644 src/main/resources/images/address_book_32.png create mode 100644 src/main/resources/images/calendar.png create mode 100644 src/main/resources/images/clock.png create mode 100644 src/main/resources/images/fail.png create mode 100644 src/main/resources/images/help_icon.png create mode 100644 src/main/resources/images/info_icon.png create mode 100644 src/main/resources/log4j2.json create mode 100644 src/main/resources/view/ActivityHistory.fxml create mode 100644 src/main/resources/view/ActivityHistoryCard.fxml create mode 100644 src/main/resources/view/DarkTheme.css create mode 100644 src/main/resources/view/DefaultBrowserPlaceHolderScreen.fxml create mode 100644 src/main/resources/view/Extensions.css create mode 100644 src/main/resources/view/Help.fxml create mode 100644 src/main/resources/view/PersonListCard.fxml create mode 100644 src/main/resources/view/PersonListPanel.fxml create mode 100644 src/main/resources/view/RootLayout.fxml create mode 100644 src/main/resources/view/StatusBarFooter.fxml create mode 100644 src/main/resources/view/TagList.fxml create mode 100644 src/main/resources/view/TagListCard.fxml create mode 100644 src/test/data/XmlUtilTest/empty.xml create mode 100644 src/test/data/XmlUtilTest/tempAddressBook.xml create mode 100644 src/test/data/XmlUtilTest/validAddressBook.xml create mode 100644 src/test/java/address/TestApp.java create mode 100644 src/test/java/address/parser/ParserTest.java create mode 100644 src/test/java/address/storage/StorageManagerTest.java create mode 100644 src/test/java/address/testutil/AddressBookBuilder.java create mode 100644 src/test/java/address/testutil/BaseEventSubscriber.java create mode 100644 src/test/java/address/testutil/PersonBuilder.java create mode 100644 src/test/java/address/testutil/ScreenShotRule.java create mode 100644 src/test/java/address/testutil/SerializableTestClass.java create mode 100644 src/test/java/address/testutil/TestDataGenerator.java create mode 100644 src/test/java/address/testutil/TestUtil.java create mode 100644 src/test/java/address/testutil/TypicalTestData.java create mode 100644 src/test/java/address/util/FilteredListTest.java create mode 100644 src/test/java/address/util/JavaVersionTest.java create mode 100644 src/test/java/address/util/UnmodifiableObservableListTest.java create mode 100644 src/test/java/address/util/VersionTest.java create mode 100644 src/test/java/commons/FileUtilTest.java create mode 100644 src/test/java/commons/JsonUtilTest.java create mode 100644 src/test/java/commons/UrlUtilTest.java create mode 100644 src/test/java/commons/XmlUtilTest.java create mode 100644 src/test/java/guitests/FilterPersonsGuiTest.java create mode 100644 src/test/java/guitests/FullSystemTest.java create mode 100644 src/test/java/guitests/GuiRobot.java create mode 100644 src/test/java/guitests/GuiTestBase.java create mode 100644 src/test/java/guitests/guihandles/GuiHandle.java create mode 100644 src/test/java/guitests/guihandles/HeaderStatusBarHandle.java create mode 100644 src/test/java/guitests/guihandles/MainGuiHandle.java create mode 100644 src/test/java/guitests/guihandles/MainMenuHandle.java create mode 100644 src/test/java/guitests/guihandles/ManageTagsDialogHandle.java create mode 100644 src/test/java/guitests/guihandles/PersonCardHandle.java create mode 100644 src/test/java/guitests/guihandles/PersonListPanelHandle.java create mode 100644 src/test/java/guiunittests/GuiUnitTestBase.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000000..1fcf43b7cacb --- /dev/null +++ b/.gitignore @@ -0,0 +1,16 @@ +build/ +/cloud/ +.gradle/ +.idea/ +lib/* +addressbook-V*.*.*.jar +!lib/licence.jar +*.iml +*.log +*.csv +config.json +/*.xml +/update/ +src/main/resources/updater/updater*.jar +src/test/data/sandbox/ +preferences.json diff --git a/VersionData.json b/VersionData.json new file mode 100644 index 000000000000..c3af6af9035c --- /dev/null +++ b/VersionData.json @@ -0,0 +1,92 @@ +{ + "version" : "V1.6.1ea", + "libraries" : [ { + "filename" : "resource-V1.6.1ea.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/resource-V1.6.1ea.jar", + "os" : "ANY" + }, { + "filename" : "addressbook-V1.6.1ea.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/addressbook-V1.6.1ea.jar", + "os" : "ANY" + }, { + "filename" : "licence.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/licence.jar", + "os" : "ANY" + }, { + "filename" : "commons-0.0.2.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/commons-0.0.2.jar", + "os" : "ANY" + }, { + "filename" : "log4j-api-2.6.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/log4j-api-2.6.jar", + "os" : "ANY" + }, { + "filename" : "log4j-core-2.6.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/log4j-core-2.6.jar", + "os" : "ANY" + }, { + "filename" : "slf4j-simple-1.6.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/slf4j-simple-1.6.4.jar", + "os" : "ANY" + }, { + "filename" : "commons-io-2.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/commons-io-2.4.jar", + "os" : "ANY" + }, { + "filename" : "controlsfx-8.40.10.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/controlsfx-8.40.10.jar", + "os" : "ANY" + }, { + "filename" : "jackson-datatype-jsr310-2.7.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jackson-datatype-jsr310-2.7.4.jar", + "os" : "ANY" + }, { + "filename" : "guava-19.0.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/guava-19.0.jar", + "os" : "ANY" + }, { + "filename" : "jxbrowser-win-6.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jxbrowser-win-6.4.jar", + "os" : "WINDOWS" + }, { + "filename" : "jxbrowser-mac-6.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jxbrowser-mac-6.4.jar", + "os" : "MAC" + }, { + "filename" : "jxbrowser-linux32-6.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jxbrowser-linux32-6.4.jar", + "os" : "LINUX32" + }, { + "filename" : "jxbrowser-linux64-6.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jxbrowser-linux64-6.4.jar", + "os" : "LINUX64" + }, { + "filename" : "jkeymaster-1.2.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jkeymaster-1.2.jar", + "os" : "ANY" + }, { + "filename" : "jackson-annotations-2.7.0.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jackson-annotations-2.7.0.jar", + "os" : "ANY" + }, { + "filename" : "jxbrowser-6.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jxbrowser-6.4.jar", + "os" : "ANY" + }, { + "filename" : "jna-4.2.1.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jna-4.2.1.jar", + "os" : "ANY" + }, { + "filename" : "jackson-core-2.7.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jackson-core-2.7.4.jar", + "os" : "ANY" + }, { + "filename" : "jackson-databind-2.7.4.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/jackson-databind-2.7.4.jar", + "os" : "ANY" + }, { + "filename" : "slf4j-api-1.7.13.jar", + "downloadLink" : "https://github.com/HubTurbo/addressbook/releases/download/Resources/slf4j-api-1.7.13.jar", + "os" : "ANY" + } ] +} \ No newline at end of file diff --git a/build.gradle b/build.gradle new file mode 100644 index 000000000000..3d74fcac69e1 --- /dev/null +++ b/build.gradle @@ -0,0 +1,359 @@ +/* + * Gradle Configuration File + * + * For more details take a look at the Java Quickstart chapter in the Gradle + * user guide available at http://gradle.org/docs/2.2.1/userguide/tutorial_java_projects.html + */ + +plugins { + id "com.github.kt3k.coveralls" version "2.4.0" + id 'com.github.johnrengelman.shadow' version '1.2.3' +} + +allprojects { + version = 'V1.6.1ea' + + apply plugin: 'idea' + apply plugin: 'java' + apply plugin: 'checkstyle' + checkstyle { + toolVersion = '6.19' + sourceSets = [project.sourceSets.main] + } + apply plugin: 'pmd' + apply plugin: 'findbugs' + findbugs { + excludeFilter = file("${projectDir}/config/findbugs/excludeFilter.xml") + } + apply plugin: 'jacoco' + + sourceCompatibility = JavaVersion.VERSION_1_8 + targetCompatibility = JavaVersion.VERSION_1_8 + + repositories { + jcenter() + mavenCentral() + maven { url "https://repo.eclipse.org/content/repositories/egit-releases/" } + maven { url "https://oss.sonatype.org/content/repositories/snapshots/" } + } + + // This part is similar to global variables + // Access them by using double-quoted strings (GStrings) and referencing by $ e.g. "Variable contains $Variable" + project.ext { + controlsFxVersion = '8.40.11' + guavaVersion = '19.0' + jacksonVersion = '2.7.0' + jacksonDataTypeVersion = '2.7.4' + jKeyMasterVersion = '1.2' + junitVersion = '4.12' + log4jVersion = '2.6' + mockServerVersion = '3.10.1' + testFxVersion = '3.1.0' + monocleVersion = '1.8.0_20' + slf4jSimpleVersion = '1.6.4' + commonsIoVersion = '2.4' + + // Commons and Updater versions to be updated here on release + commonsArchiveName = 'commons-0.0.2.jar' + + mainAppArchiveName = 'resource-' + project.version + '.jar' + mainAppMainClass = 'address.MainApp' + + installerArchiveName = 'installer-' + project.version + '.jar' + installerMainClass = 'installer.Installer' + + launcherArchiveName = 'addressbook-' + project.version + '.jar' + launcherMainClass = 'launcher.Launcher' + + generateVersionDataMainClass = 'address.util.VersionDataGenerator' + libDir = 'lib' + } + + jacocoTestReport { + reports { + xml.enabled false + csv.enabled false + html.destination "${buildDir}/jacocoHtml" + } + } + + sourceSets { + main { + java { + srcDir 'src/main/java' + include('address/**/*') + include('hubturbo/**/*') + include('commons/**/*') + } + resources { + srcDir 'src/main/resources' + } + } + commons { + java { + srcDir 'src/main/java' + include('commons/**/*') + } + } + installer { + java { + srcDir 'src/main/java/' + include('installer/**/*') + include('commons/**/*') + } + resources { + srcDir "." + include("$libDir/*") + include("$launcherArchiveName") + include('VersionData.json') + } + } + launcher { + java { + srcDir 'src/main/java' + include('launcher/**/*') + } + } + generateVersionData { + java { + srcDir 'src/main/java/' + } + } + } + + dependencies { + compile "org.apache.logging.log4j:log4j-api:$log4jVersion" + compile "org.apache.logging.log4j:log4j-core:$log4jVersion" + compile "org.slf4j:slf4j-simple:$slf4jSimpleVersion" // Required to suppress warning, for jkeymaster, see http://www.slf4j.org/codes.html#StaticLoggerBinder + compile "commons-io:commons-io:$commonsIoVersion" + compile "org.controlsfx:controlsfx:$controlsFxVersion" + compile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" + compile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonDataTypeVersion" + compile "com.google.guava:guava:$guavaVersion" + compile "org.controlsfx:controlsfx:$controlsFxVersion" + compile "com.github.tulskiy:jkeymaster:$jKeyMasterVersion" + compile files("$libDir/$commonsArchiveName") + + testCompile "junit:junit:$junitVersion" + testCompile "org.testfx:testfx-core:4.0.+" + testCompile "org.testfx:testfx-junit:4.0.+" + testCompile "org.testfx:testfx-legacy:4.0.+", { + exclude group: "junit", module: "junit" + } + + testCompile "org.testfx:openjfx-monocle:$monocleVersion" + + installerCompile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" + installerCompile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonDataTypeVersion" + installerCompile "org.apache.logging.log4j:log4j-api:$log4jVersion" + installerCompile "org.apache.logging.log4j:log4j-core:$log4jVersion" + + commonsCompile "com.fasterxml.jackson.core:jackson-databind:$jacksonVersion" + commonsCompile "com.fasterxml.jackson.datatype:jackson-datatype-jsr310:$jacksonDataTypeVersion" + commonsCompile "org.apache.logging.log4j:log4j-api:$log4jVersion" + commonsCompile "org.apache.logging.log4j:log4j-core:$log4jVersion" + } + + configurations { + generateVersionDataCompile { + extendsFrom compile + } + } + + // Remove any old custom dependencies + task deleteOldCustomDependenciesFromLibrary(type: Delete) { + delete fileTree(dir: ".", include: 'addressbook-V*.*.*.jar') + delete fileTree(dir: "$libDir", includes: ['commons-*.*.*.jar', 'resource-V*.*.*.jar']) + } + + // Copy downloaded dependencies from Gradle's cache into the library directory + task copyAllDependenciesToLibrary(type: Copy) { + from(configurations.compile) { + } + into "$libDir/" + } + copyAllDependenciesToLibrary.dependsOn deleteOldCustomDependenciesFromLibrary + copyAllDependenciesToLibrary.dependsOn clean + + // Commons jar + task createCommonsJar(type: Jar) { + archiveName = "$commonsArchiveName" + + from sourceSets.commons.output + destinationDir = file("$libDir/") + } + createCommonsJar.dependsOn copyAllDependenciesToLibrary + compileCommonsJava.mustRunAfter copyAllDependenciesToLibrary + + jacoco { + toolVersion = "0.7.5.201505241946" + } +} + +task wrapper(type: Wrapper) { + gradleVersion = '2.12' +} + +tasks.withType(FindBugs) { + reports { + xml.enabled = false + html.enabled = true + } +} + +task coverage(type: JacocoReport) { + sourceDirectories = files(allprojects.sourceSets.main.allSource.srcDirs) + classDirectories = files(allprojects.sourceSets.main.output) + executionData = files(allprojects.jacocoTestReport.executionData) + afterEvaluate { + classDirectories = files(classDirectories.files.collect { + fileTree(dir: it, exclude: ['**/*.jar']) + }) + } + reports { + html.enabled = true + xml.enabled = true + } +} + +coveralls { + sourceDirs = allprojects.sourceSets.main.allSource.srcDirs.flatten() + jacocoReportPath = "${buildDir}/reports/jacoco/coverage/coverage.xml" +} + +tasks.coveralls { + dependsOn coverage + onlyIf { System.env.'CI' } +} + +task checkStyle { // A dummy task to run a set of tasks +} + +class AddressBookTest extends Test { + public AddressBookTest() { + forkEvery = 1 + systemProperty 'testfx.setup.timeout', '60000' + } + + public void setHeadless() { + systemProperty 'java.awt.robot', 'true' + systemProperty 'testfx.robot', 'glass' + systemProperty 'testfx.headless', 'true' + systemProperty 'prism.order', 'sw' + systemProperty 'prism.text', 't2k' + } +} + +task guiTests(type: AddressBookTest) { + include 'guitests/**' + jacoco { + destinationFile = new File("${buildDir}/jacoco/test.exec") + } +} + +task guiUnitTests(type: AddressBookTest) { + include 'guiunittests/**' + jacoco { + destinationFile = new File("${buildDir}/jacoco/test.exec") + } +} + +task unitTests(type: AddressBookTest) { + include 'address/**' + include 'commons/**' + + jacoco { + destinationFile = new File("${buildDir}/jacoco/test.exec") + } +} + +// Task to run specified test classes in headful mode +task headfulTests(type: AddressBookTest) { + // Have to separate by test classes instead of test methods since we cannot define methods to be excluded using TestFilter (for allTests) + // See https://docs.gradle.org/current/javadoc/org/gradle/api/tasks/testing/TestFilter.html for the list of its functionalities + // include 'guitests/FullSystemTest.class' + jacoco { + destinationFile = new File("${buildDir}/jacoco/test.exec") + } +} +headfulTests.onlyIf { + headfulTests.includes.size() > 0 +} + +// Task to run specified test classes in headless mode +task headlessTests(type: AddressBookTest) { + // include '' + jacoco { + destinationFile = new File("${buildDir}/jacoco/test.exec") + } +} +headlessTests.onlyIf { + headlessTests.includes.size() > 0 +} +headlessTests.setHeadless() + +// Task to run all tests asides from those specified in headfulTests and headlessTests +// Test mode depends on whether headless task has been run +task allTests(type: AddressBookTest) { + exclude headfulTests.includes + exclude headlessTests.includes + jacoco { + destinationFile = new File("${buildDir}/jacoco/test.exec") + } +} + +task headless << { + println "Setting headless mode properties." + guiTests.setHeadless() + guiUnitTests.setHeadless() + unitTests.setHeadless() + allTests.setHeadless() +} + +task headlessForMac << { + println "Setting headless mode properties." + guiTests.setHeadless() + guiUnitTests.setHeadless() + unitTests.setHeadless() + allTests.setHeadless() + + String osName = System.getProperty("os.name"); + if (osName.contains("Mac OS")) { + println "Mac OS detected, removing disruptive headless tests" + + List disruptiveHeadlessUnitTestsOnMac = new ArrayList() + disruptiveHeadlessUnitTestsOnMac.add('address/keybindings/BindingsTest.class') + disruptiveHeadlessUnitTestsOnMac.add('address/keybindings/GlobalHotkeyProviderTest.class') + disruptiveHeadlessUnitTestsOnMac.add('address/keybindings/GlobalHotkeyTest.class') + disruptiveHeadlessUnitTestsOnMac.add('address/keybindings/KeyBindingsManagerApiTest.class') + disruptiveHeadlessUnitTestsOnMac.add('address/keybindings/KeyBindingsManagerTest.class') + + List disruptiveHeadlessGuiUnitTestsOnMac = new ArrayList() + List disruptiveHeadlessGuiTestsOnMac = new ArrayList() + + unitTests.excludes.addAll(disruptiveHeadlessUnitTestsOnMac) + guiUnitTests.excludes.addAll(disruptiveHeadlessGuiUnitTestsOnMac) + guiTests.excludes.addAll(disruptiveHeadlessGuiTestsOnMac) + allTests.excludes.addAll(disruptiveHeadlessUnitTestsOnMac) + allTests.excludes.addAll(disruptiveHeadlessGuiUnitTestsOnMac) + allTests.excludes.addAll(disruptiveHeadlessGuiTestsOnMac) + + } +} + +// Makes sure that headless properties are set before running tests +unitTests.mustRunAfter headless +guiUnitTests.mustRunAfter headless +guiTests.mustRunAfter headless +allTests.mustRunAfter headless + +headless.shouldRunAfter checkStyle +unitTests.shouldRunAfter checkStyle +guiUnitTests.shouldRunAfter checkStyle +guiTests.shouldRunAfter checkStyle +allTests.shouldRunAfter checkStyle + +checkStyle.shouldRunAfter clean +checkStyle.dependsOn checkstyleMain, checkstyleTest, findbugsMain, findbugsTest, pmdMain, pmdTest + +defaultTasks 'clean', 'headless', 'allTests', 'coverage' diff --git a/config/checkstyle/checkstyle-noframes-sorted.xsl b/config/checkstyle/checkstyle-noframes-sorted.xsl new file mode 100644 index 000000000000..9c0ac3054165 --- /dev/null +++ b/config/checkstyle/checkstyle-noframes-sorted.xsl @@ -0,0 +1,195 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ +

CheckStyle Audit

Designed for use with CheckStyle and Ant.
+
+ + + +
+ + + +
+ + + + +
+ + + + +
+ + + + +

Files

+ + + + + + + + + + + + + + +
NameErrors
+
+ + + + +

File

+ + + + + + + + + + + + + + +
Error DescriptionLine
+ Back to top +
+ + + +

Summary

+ + + + + + + + + + + + +
FilesErrors
+
+ + + + a + b + + +
+ + diff --git a/config/checkstyle/checkstyle.xml b/config/checkstyle/checkstyle.xml new file mode 100644 index 000000000000..3bab4e05bbae --- /dev/null +++ b/config/checkstyle/checkstyle.xml @@ -0,0 +1,326 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/config/findbugs/excludeFilter.xml b/config/findbugs/excludeFilter.xml new file mode 100644 index 000000000000..03c15ae4cc81 --- /dev/null +++ b/config/findbugs/excludeFilter.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + diff --git a/copyright.txt b/copyright.txt new file mode 100644 index 000000000000..93aa2a39ce25 --- /dev/null +++ b/copyright.txt @@ -0,0 +1,9 @@ +Some code adapted from http://code.makery.ch/library/javafx-8-tutorial/ by Marco Jakob + +Copyright by Susumu Yoshida - http://www.mcdodesign.com/ +- address_book_32.png +- AddressApp.ico + +Copyright by Jan Jan Kovařík - http://glyphicons.com/ +- calendar.png +- edit.png diff --git a/data/addressbook.xml b/data/addressbook.xml new file mode 100644 index 000000000000..3fb298938833 --- /dev/null +++ b/data/addressbook.xml @@ -0,0 +1,57 @@ + + + + 2120166986 + John + Lim + + + + + + + 1 + second + person + + + + + + + 1 + second + person + + + + + + + 1 + second + person + + + + + + + 1 + second + person + + + + + + + 1 + second + person + + + + + + diff --git a/docs/addressbook/CloudSimulation.md b/docs/addressbook/CloudSimulation.md new file mode 100644 index 000000000000..048cf3e750d3 --- /dev/null +++ b/docs/addressbook/CloudSimulation.md @@ -0,0 +1,78 @@ +In order to simulate the idea of a cloud and our interactions with it, we have implemented 3 key components: `RemoteManager`, `RemoteService` and `CloudSimulator` + +They are arranged as follows: +> ... -> SyncManager <---> RemoteManager <---> RemoteService <---> CloudSimulator + +# Key responsibilities +## RemoteManager +- Provides a high-level API for the local model to communicate with the server without having to be involved in too many details +- Responsible for converting API calls into the cloud calls + - Uses paged requests to get the full list of resources (`Tag` and `Person`) +- Keeps track of information likely needed for the next update request to the remote e.g. **last** updated `time`, `ETag` and list of `Tags` + +## RemoteService +- Responsible for converting cloud responses into localised responses + - Parses the stream of the content returned from the cloud (which is originally in JSON format). This applies to both the header and the body content +- Deals with both local model and service model, and converts between them when needed +- Returns an `ExtractedRemoteResponse`, which contains the limit details as well as the returned (and converted) content that has been instantiated in memory + +## CloudSimulator +- Provides a similar set of API as GitHub +- Writes the content of addressbooks into individual files, based on their names + - For example, an addressbook `myContacts` will be written into `cloud/myContacts` and an addressbook `hisContacts` will be written into `cloud/hisContacts`. +- Maintains the API count of the current session + - Similar to GitHub (authenticated), it has an API limit of 5000 by default, and resets at every hour's mark + - The reset is based on a `TickingTimer` running in the background i.e. the reset is run after a given time delay +- Responses are returned as `RemoteResponse` + - ALWAYS (almost) contains the rate limit information. e.g. rate limit remaining and its next reset time + - The only time it doesn't is if there is an `internal server error` (500) + - ALWAYS contains the response code that indicates the status of the request. This response code is similar to its respective HTTP status code + - May contain content such as a `CloudPerson` object in JSON form, depending on the query and its success status +- If initialised with a `true` boolean, there will be chance of data modifications before the result is returned + - For lists, there may be `additions` and `modifications` + - For single objects, there may be `modifications` + - `additions` and `modifications` are both considered `mutations` + +# Notes +## Response code +- Behaves similarly to `GitHub` + - `20x` if request is successful. `x` depends on the type of request + - `304` if there are no changes since the last response + - These responses will not include the page relations + - `400 - Bad Request` if the arguments given are invalid + - `403 - Forbidden` if there is no more API quota left + - `500 - Internal Server Error` if there is an error on the cloud. e.g. error reading from the cloud file + +## CloudAddressBook +- Just a simple wrapper for cloud data, so that a single addressbook can be stored in a single cloud file + +## CloudPerson +- Whenever its field are updated, `lastUpdatedAt` will also be set to the current time. +- Whenever it is deleted, it still remains in the list with `isDeleted` set to true + +## CloudTag +- Whenever it is deleted, it is completely removed from the list of tags +- Does not have a `lastUpdatedAt` field + +# Other notes related to HT +- `Last-Modified` and `If-Modified-Since` headers do not seem to be useful + +`Last-Modified` (response) and `If-Modified-Since` (request) headers seem to only apply to single-objects that have the `updatedAt` field (`issues` and `milestones`). The `Last-Modified` header field will also have the same value as `updatedAt`. This means that these do not provide us more information than we already have. + +- We can check updates for `labels` and `collaborators` without incurring API usage (Invariant: they do not scale, at least not quickly) + +We need to simply save the `ETag` of the requests and use them in further calls. If the responses are that they are `304 Not Modified`, then it will not incur API usage. + +- We are able to reduce the number of API calls for getting updated issues +For `issues` and `issue comments`, we can try to do something similar to `labels` and `collaborators` (see above). However, since the requests are paged, we have increased workload if we were to use `ETag`s to check for updates. For example, having 1000 issues will require up to 10 requests using the old `ETag`s, to ensure that none of the pages have changed. Results will also have to in descending order, since new issues will change all pages' `ETag`s if in ascending order) + +We can instead just make a new `updated-since` request based on the date saved together with the last ETag. Any update requests will result in **>=1 (number of updated issues/issues per page)** API usage. However, even if there are no updates, 1 API quota will be used. + +Note: HT has not implemented retrieval of `issue comments` yet. The indicator we have now on the issue card is simply a count, which is included in the issue's information. + +## Problem: milestones +`Milestones` is a scalable resource (grows with the repo), and yet we are unable to retrieve `milestones` that have been modified after a certain time, like we can do with `issues` and `issue comments`. +One way to get around this is probably to extract information from the obtained `issues`, where each contains its assigned `milestone` information, and attempt to determine how the list of `milestones` has changed. +- Then, how do we know if a milestone has been unassigned? I guess the only way is to verify against the local model every time an issue has been updated, which may cost additional performance +- If a milestone of 1 issue is changed and the issue is demilestoned, we won't know that the milestone has changed since the issue updates won't reflect that. +- Maybe the best way is still to follow how we obtain updates for `labels` and `collaborators`, but the update may be slow. Could be mitigated by having a lower frequency of milestone updates diff --git a/docs/addressbook/Configuration.md b/docs/addressbook/Configuration.md new file mode 100644 index 000000000000..0f87979bb1b2 --- /dev/null +++ b/docs/addressbook/Configuration.md @@ -0,0 +1,46 @@ +# Configuration + +Certain properties of the application can be customized through the configuration file (default: `config.json`): + +- Logging level +- Update interval +- Number/Type of browser(s) used +- Local Data file +- Cloud data file +- Name of addressbook +- Application title + +Most of the variable names are rather straightforward. However, the logging section is slightly more complex and will be elaborated on. + +Behaviours to note: +- The application creates a config file with default values if no existing config file can be found. +- Configuration is reset to default if file cannot be read (including if the file contains unexpected or has missing parameters) + +# Logging +There are 2 configuration variables meant for logging: +- `currentLogLevel` + - Specifies the application-wide logging level +- `specialLogLevel` + - Adding `className` : `loggingLevel` pairs to this variable will impose a special logging level for that class (priority over the application-wide logging level) + +Here is a typical example of the logging parameters in `config.json`: +``` +... + "currentLogLevel" : "DEBUG", + "specialLogLevels" : { + "ModelManager" : "TRACE", + "SyncManager" : "TRACE" + }, +... +``` +Such a configuration cause the loggers to log messages at the `DEBUG` level throughout the application, except `ModelManager`'s and `SyncManager`'s loggers which will log messages at the `TRACE` level. + +**Note that these settings do not apply to most of `Config`, and also `StorageManager`'s code to read the configuration file since they are not yet in effect. Therefore the logging will usually be at the default level for `Config` and for the affected parts of `StorageManager`. This default logging level is specified in `resources/log4j2.json`.** + +# Introducing new configuration variables +(For developers) +- Ensure that a default value is assigned to the desired variable. +- Add getters and setters for the new variable +- Get the config object from respective component managers (see `MainApp::initComponents`) + - Add `Config` to the constructor parameters if they do not already accept a `Config` object + - DO NOT obtain the configuration object directly from `StorageManager` as such practices tend to hide application state information during testing. diff --git a/docs/addressbook/KeyboardShortcuts.md b/docs/addressbook/KeyboardShortcuts.md new file mode 100644 index 000000000000..4d58885aa315 --- /dev/null +++ b/docs/addressbook/KeyboardShortcuts.md @@ -0,0 +1,22 @@ +#Key Bindings +> Note: On Mac OS, use in place of Ctrl + +##Shortcuts + +| Shortcut | Action | +| ------------------------------------: |--------| +| A | t**A**g the selected person | +| D | **D**elete the selected person | +| E | **E**dit the selected person | +| G,B | **G**o to **B**ottom of the list | +| G,T | **G**o to **T**op of the list | +| Ctrl + Down | Jump from filter box to person list | +| Ctrl + Number | Jump to Nth item in the person list, N=1..9 | + +##Hotkeys +Unlike keyboard shortcuts given above, hotkeys work even when the app is not in focus. + +| Shortcut | Action | +| ------------------------------------: |--------| +| Ctrl + Alt + X | Minimize app | +| Ctrl + Shift + X | Toggle between maximum and default app Window sizes| diff --git a/docs/addressbook/Logging.md b/docs/addressbook/Logging.md new file mode 100644 index 000000000000..d89355641219 --- /dev/null +++ b/docs/addressbook/Logging.md @@ -0,0 +1,56 @@ +# Logging + +We are using [log4j2](http://logging.apache.org/log4j/2.x/) as our logger, and `AppLogger` is used as a wraparound of the logger to control the logging levels programmatically according to our requirements. + +**Note to developers** +- The logging level can be controlled using the `loggingLevel` setting in the configuration file (See [Configuration](../docs/Configuration.md)). +- The `AppLogger` for a class can be obtained using `LoggerManager.getLogger(Class)` which will log messages according to `src/main/resources/log4j2.json`'s specified patterns +- Currently log messages are output through 3 methods: `Console`, `.log` and `.csv`. Since the CSV file is using `;` as its delimiter, developers should avoid using `;` which may lead to incorrect interpretation of log data. + + +**log4j2 information** +- Supposedly optimized version of `LogBack` +- Can also act as an API for other logger implementations such as `slf4j` + - `log4j2` has its internal implementation, but can be substituted as needed + - Our application uses the default internal implementation + +# Guidelines + +## Logging Levels + +- FATAL + - Critical use case affected, which may possibly cause the termination of the application + +- ERROR **(NOT USED)** + +- WARN + - Can continue, but with caution + +- INFO + - Information important for the application's purpose + - e.g. update to local model/request sent to cloud + - Information that the layman user can understand + +- DEBUG + - Used for superficial debugging purposes to pinpoint components that the fault/bug is likely to arise from + - Should include more detailed information as compared to `INFO` i.e. log useful information! + - e.g. print the actual list instead of just its size + +- TRACE + - Everywhere, e.g. entry and exit of ALL methods, including the parameters being given/returned + - Will not be using log4j2 for this, will explore AOP at a future date + +## General + +- Possible guidelines to adhere to: + - Include version of code being run in EVERY file + - Don't manually concatenate strings, which may worsen performance + - Instead, use log4j2's `{}` to log parameters + - `Catch` blocks that re-throw the exception should not log the exception as this may lead to repeated logging of the same exception + - Avoid `NullPointerException`s, which may occur if we try to log some return value of an object's method, when the object itself may be null + - Ensure that the object being printed has `toString()` implemented, if not the printout will be uninformative such as `Object@31942` + +## References and readings +- [Apache Commons Logging guide](http://commons.apache.org/proper/commons-logging/guide.html#Message_PrioritiesLevels) +- [10 Tips for Proper Application Logging](https://www.javacodegeeks.com/2011/01/10-tips-proper-application-logging.html) +- [Base 22 Java Logging Standards and Guidelines](https://wiki.base22.com/display/btg/Java+Logging+Standards+and+Guidelines) diff --git a/docs/images/Release Cycle.jpg b/docs/images/Release Cycle.jpg new file mode 100644 index 0000000000000000000000000000000000000000..aa0340f1d7d0a52af5af0172b2004d9d4d475e77 GIT binary patch literal 46942 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 ZQd)Q(Rx?%eZM}IBcy?^yfMEImHv#n_BKrUU literal 0 HcmV?d00001 diff --git a/docs/releases/Automatic Update Architecture.md b/docs/releases/Automatic Update Architecture.md new file mode 100644 index 000000000000..57196d88518d --- /dev/null +++ b/docs/releases/Automatic Update Architecture.md @@ -0,0 +1,40 @@ +# Update Architecture +We enabled our application to be self-updatable, and downloads only the required components when doing so. + +For more detailed information of the individual components, please see: + - [Launcher](Launcher.md) + - [Updater](Updater.md) + +For more detailed information on how we make a release with this architecture, please see: + - [Release](Release.md) + +## Application Usage Flow - From downloading to updating +1. The user downloads the `installer` from the website, and saves it in a desired directory. +2. Upon execution of the `installer`, it unpacks the required files for the application into the same directory. + - Launcher + - Library files (including the main application) + - Config file +3. `Installer` also starts the `launcher` before it closes. +4. `Launcher` launches the `main application` with its run-time dependencies, as well as any desired arguments. +5. The `main application` runs, and checks for updates in the background. + - If there is an update, it will download any required libraries, produce a specification file with information about these files and extract an internal `updater` resource in preparation for the upgrade. Required libraries may also include a new version of the `launcher`. +6. Upon exit of the main application, the extracted `updater` resource is executed. + - This JAR is a fat JAR and already contains required dependencies e.g. to read the specification file. +7. The `updater` resource will read the specification file, and perform the needed operations for version upgrade. + - After updating, this resource will close and be left in the directory, and will probably be overwritten in the next update + +## Constraints and Considerations + +Here are some of the constraints we faced during the design of the update architecture: + +- On Windows, dependencies used during program runtime cannot be modified + - A separate executable has to be used to perform updates to the application. + + - Possible solution A: Create a launcher that checks for new versions and performs updates. + - However, we would also like to make updates to this separate executable. + - Possible solution B: Also have an update component in the main application, so the main application and the executable can update each other perpetually. + - However, this will result in duplicate update codes, and the update status cannot be shown on the main application. (At least not in a straightforward way. I guess we could still write the update status to a file and make the main app poll for status changes.) + + - Current solution: Main application extracts an internal resource which is launched to perform the update. + - Drawbacks: We cannot update only the updater itself, and the main application will be slightly bloated since it requires some similar dependencies as the updater. + - Possible optimization: Change updater into a non-fat JAR. Its dependencies could be copied by the main application into the same directory as the extracted updater jar. diff --git a/docs/releases/Launcher.md b/docs/releases/Launcher.md new file mode 100644 index 000000000000..88e50fcddc9d --- /dev/null +++ b/docs/releases/Launcher.md @@ -0,0 +1,2 @@ +# Launcher +We compiled a simple launcher to launch our main application, typically with the name `addressbook-V...jar`. This is so that we are able to customize launch arguments for the main application. \ No newline at end of file diff --git a/docs/releases/Release.md b/docs/releases/Release.md new file mode 100644 index 000000000000..60a3c01ed7a5 --- /dev/null +++ b/docs/releases/Release.md @@ -0,0 +1,163 @@ +# Release + +## Background Information +### Versioning +An `addressbook` version has the format of `V..` with suffix of `ea` if it is an early access, e.g. +`V1.0.0ea` for early access version and `V1.0.0` for stable version. + +- `MAJOR` version is bumped up when a release introduces major changes to the application. +- `MINOR` version is bumped up on every release when `MAJOR` version does not change. +- `PATCH` version is bumped up when there is a hotfix needed for the current release version. + +This versioning system is loosely based on [Semantic Versioning](http://semver.org/). + +### Release cycle +`addressbook` has 3 branches for release, namely `master`, `early-access` and `stable`: +- `master` : development will occur in this branch. +- `early-access`: when we have a release candidate from master, the latest commit in `master` will be merged to this +branch and an early access release will be made for people to try out the new features. This release candidate will be +polished in this branch, i.e. any bug fixes to early access version will go to this branch, increasing the `PATCH` version number +(e.g. from `V1.1.-0-ea` to `V1.1.-1-ea`). This change is then merged back to master. + + If there are more features to be published for people to try out, a new early access release will be created with a bump +in `MINOR` version number, resetting the `PATCH` version number to 0. + + This release channel is not meant for production use. Those who are interested to try out the latest (possibly unstable) +features of Addressbook should use this early access version. +- `stable` : Once `early-access` version has enough polished features to be included in the new version for +production use, it will be released as a stable version with the same version number as the `early-access` version, +excluding the `ea` mark of `early-access` version. If there is any hotfix to stable version, it will be done in this branch, +bumping up the `PATCH` version number (e.g. from `V1.1.-1-` to `V1.1.-2-`). This change is then merged to `early-access` +branch with a bump in `early-access` `PATCH` version number from whatever it is (e.g. from `V1.2.-0-ea` to `V1.2.-1-ea`) +and this will then be merged to `master`. + +To illustrate, look at the diagram below. + + + +Development in master does not stop, even after creating an early access release. Version does not matter as well in master. + +On creating an early access release, merge the `master` branch to `early-access` branch and create a release (instruction below) +with the `PATCH` version of 0, like `V1.0.0ea` and `V1.1.0ea` in the diagram. If the release has a bug, it will be fixed in +`early-access` branch, and the bug fix needs to be merged back `master` branch as shown in `V1.0.1ea`. + +When `early-access` version has a set of new features that is well polished, we will create a stable release by merging +`early-access` branch to `stable` branch and create a release. The version of the release uses the latest `early-access` +version without early access flag (from `V1.0.2ea` to `V1.0.2` and from `V1.1.2ea` to `V1.1.2` in the diagram). + +If the stable release requires a bug fix, the bug fix will be done in `stable` branch. A re-release will be done with +a bump in `PATCH` version, as shown in `V1.0.3` in the diagram. This bug fix will be merged back to `early-access` branch +in which we will re-release the early access version with a bump in early access `PATCH` version, as shown in `V1.1.0ea` +to `V1.1.1ea` in the diagram (and not to `V1.0.3ea`). This bug fix will also be merged to `master` branch from +`early-access` branch. + + +References to how teams have multiple release channel: +- http://blog.atom.io/2015/10/21/introducing-the-atom-beta-channel.html +- https://docs.google.com/presentation/d/1uv_dNkPVlDFG1kaImq7dW-6PasJQU1Yzpj5IKG_2coA/present?slide=id.i0 +- http://blog.rust-lang.org/2014/10/30/Stability.html + +## Creating a Release + +### How to create a release +1. Merge the appropriate branches + - Releasing an early-access version: Merge `master` branch to `early-access` branch. +Releasing a stable version: Merge `early-access` branch to `stable` branch. + - Use `git merge --no-commit --no-ff` (LOCALLY) so that a merge commit won't be made, in which you can make relevant changes (e.g. changing version number and early access flag) before committing with the version of the software as the commit message. +2. Update version numberings + - Update the versions of the application and its dependencies in `MainApp` and in `build.gradle`. + - If the main application is an early access version, set `IS_EARLY_ACCESS` in `MainApp` as `true` and add `ea` at the end of version in `build.gradle`. + - For custom dependencies, manually check the commits to see if its package has been modified since the last release. If it has, modify its version numbering (usually bumping the minor or major version, according to semantic versioning). +3. Compile the custom dependencies + - Run `gradle` task `createInstallerJar` +4. Update version data + - Generate partially-updated `VersionData.json` containing the list of libraries and information + - Run `gradle` task `generateVersionData` + - Information of unchanged libraries will be copied from the old `VersionData.json` + - The console will print a list of libraries which needs to be manually updated for the new version + - Upload all updated libraries + - Upload them to https://github.com/HubTurbo/addressbook/releases/tag/Resources + - Update `VersionData.json` + - Open `VersionData.json` and manually update the new fields accordingly + - Fill in the links to download the new libraries. + - Change the OS compatibility of the new libraries to ensure that only the libraries relevant to an OS will be loaded and checked +5. Create a new commit + - Commit and push the files for release - name the commit `V..` (with suffix `ea` if it's an early access version) + - This is so that the following GitHub release (done in Step 7) will appropriately tag this commit containing the updated `VersionData.json` +6. Create the release JAR by running the Gradle task `createInstallerJar` + - this must be run again to use the updated `VersionData.json` +7. Draft a new release in [GitHub](https://github.com/HubTurbo/addressbook/releases) and tag the corresponding branch (`early-access` or `stable`) + - Use the version number for both the tag and the title. +8. Upload the generated `installer-V*.*.*.jar` found at `build/libs` to the latest release. +9. Publish! + +## More About Release +The main application of `addressbook` is configured to be released as a [non-fat JAR](http://stackoverflow.com/questions/19150811/what-is-a-fat-jar), +that is, libraries that it depends on will not be included inside the `addressbook` JAR itself. Instead, the JARs of these libraries +will be put in `lib` folder which can then be included during runtime by setting the classpath to include the `lib` folder. + +For example: +``` +addressbook/ + |-> launcher.jar + |-> lib/ + |-> apache-commons-io.jar + |-> apache-logging.jar + |-> jxbrowser.jar + |-> resource.jar +``` + +This set-up is used to reduce the size of updates to be downloaded whenever a new version of `addressbook` is released. With +this set-up, only main application JAR and libraries that need to be updated will be downloaded, essentially reducing the +time taken to download updates. + +For convenience, the whole set-up mentioned above is packed into 1 JAR file +which will self-unpack itself on first run. It can be deleted after installation, and the main application can be launched through the launcher instead. + +Several gradle tasks have been prepared to make it effortless to create a release of `addressbook` with the set-up mentioned above. + +### Gradle Tasks for Release + +The following explains the Gradle tasks that are required for a release. + +#### generateVersionData +Generates the version data (VersionData.json) which will be used by user's application instance to know if it has an update +and what to update. It reads the dependencies of the latest version of `addressbook` and put those dependencies into the version data. To make it easier for developers, it will use previous version's values for libraries that did not change. Developers then need to manually update the new dependencies information (e.g. download link, OS they are meant for). + +`generateVersionData` has its own source set which includes everything and its dependencies are extended from main application compile dependencies. This is to make it easier for generateVersionData to read any information it needs to create version data. The main class it uses is `addressbook/util/VersionDataGenerator.java` + +#### createCommonsJar +Creates the commons jar dependency. This jar contains the classes found in the `commons` package, including `FileUtil` and `OsDetector`, and is used as a dependency for most components. + +#### createUpdaterJar +Creates the updater jar dependency. This file contains the components which are required for the migration of the application to a newer version. + +See: [Updater](Updater.md) + +#### createLauncherJar +Creates launcher executable file, to launch the main application with custom arguments. + +See: [Launcher](Launcher.md) + +#### createInstallerJar +Creates the executable packed JAR for the user to download and use. The packed JAR contains the main application and all the libraries needed for the main application to run. + +Note that running this will also run the other mentioned tasks in a specified order. See `build.gradle` for more details (`dependsOn`/`mustRunAfter`). + +On first run, it unpacks the libraries and the main application, then runs the launcher. On subsequent runs, it simply attempts to restore any missing files if the current version is not newer than the installer's (i.e. has not been updated) then simply runs the launcher. However, it is expected that the user will delete this file after installation. + +### Possible Improvements + +#### Automate generateVersionData +Ideally, we don't need to host those libraries JAR on our own in GitHub release. We can grab the JARs from the Maven repositories +that `gradle` uses to get those libraries. Unfortunately, the lack of documentation of `gradle` makes it impossible to +get the URL of those JAR in the Maven repositories, hence the manual need to update the download links and upload the new +libraries to GitHub release. + +#### GUI for generateVersionData +Instead of having to deal with text file of update data which is prone to error, we can have a GUI which generateUpdateData +uses to show what libraries have been changed in the latest version with fields to update the download links. Also, +we can have a dropdown option for the OS which the libraries are needed in so developers don't need to type them manually. + +#### Installer dependencies +Use of ShadowJAR should be considered to enable logging. diff --git a/docs/releases/Updater.md b/docs/releases/Updater.md new file mode 100644 index 000000000000..137ec19d4c91 --- /dev/null +++ b/docs/releases/Updater.md @@ -0,0 +1,10 @@ +# Updater +A separate updater jar which contains key classes to perform data migration from one version to another. It is typically named as `updater.jar`. + +## How does the update work +The main application has the updater jar as an internal resource. + +1. After the main application finishes the downloading of updates, it will extract the updater jar in preparation for doing the update migration. +2. At exit of the main application, the updater jar is executed. It will attempt to perform updates for several times, expecting the main application to hog the resources to be replaced for a short while. + - Updater jar reads from the specification file created by the main application to know which files have to be moved + \ No newline at end of file diff --git a/docs/testing/Testing.md b/docs/testing/Testing.md new file mode 100644 index 000000000000..4a94773168c0 --- /dev/null +++ b/docs/testing/Testing.md @@ -0,0 +1,102 @@ +# Testing + +## Background +We are using [Gradle](https://docs.gradle.org/)'s [wrapper](https://docs.gradle.org/current/userguide/gradle_wrapper.html) to setup testing configurations and run tests. +Gradle tasks are run using `gradlew ...` on Windows and `./gradlew ...` on Mac/Linux. + +We work towards enabling **headless testing** (using [TestFX](https://github.com/TestFX/TestFX)) so that the test results will be more consistent between different machines. In addition, it will reduce disruption for the tester - the tester can continue to do his or her own work on the machine! + +Related resources: + - [Gradle's User Guide](https://docs.gradle.org/current/userguide/userguide.html) + - [Gradle's Java Plugin](https://docs.gradle.org/current/userguide/java_plugin.html) + - [Gradle's Jacoco Plugin](https://docs.gradle.org/current/userguide/jacoco_plugin.html) + - [Coveralls Gradle Plugin](https://github.com/kt3k/coveralls-gradle-plugin) + - [Travis CI Documentation](https://docs.travis-ci.com/) + + +## Tests Information + +Tests can be found in the `./src/test/java` folder. We have grouped and packaged the tests into the following types. + +1. Unit Tests - `address` and `commons` package + - Logic Testing + +2. GUI Unit Tests - `guiunittests` package + - Tests the UI interaction within a single component, and ensure its behaviour holds. + +3. System Tests - `guitests` package + - Tests the UI interaction with the user as well as the interaction between various components (e.g. passing of data) + +## Running Tests + +As mentioned, we do our testing by running gradle tasks. +Tests' settings are mostly contained in `build.gradle` and `.travis.yml`. + +### Available Gradle Tasks +There are a few key gradle tasks defined that we can play around with: +#### Testing +###### Flexible test mode +- `allTests` to run all tests + - except those in `headlessTests` and `headfulTests` +- `guiTests` to run tests in the `guitests` package +- `guiUnitTests` to run tests in the `guiunittests` package +- `unitTests` to run tests in the `address` package + +###### Fixed test mode +- `headlessTests` to run specified tests in headless mode +- `headfulTests` to run specified tests in headful mode + + +#### Test mode +- `headless` to indicate headless mode + - applies only to tests with flexible test mode +- `headlessForMac` + - In addition to the default `headless` task, this also avoids running some tests that disrupt the user's workflow on Mac + +#### Others +- `checkStyle` to run code style checks + - `PMD`, `FindBugs` and `Checkstyle` +- `clean` to remove previously built files + - Running tasks repeatedly may not work unless the build files are `clean`ed first. +- `coverage` to generate coverage information after tests have been run + - Generated coverage reports can be found at `./build/reports/jacoco/coverage/coverage.xml` +- `coveralls` to upload data from CI services to coveralls.io (no reason to run this locally) + +### Local Testing +#### How to do some common testing-related tasks +- To run all tests in headless mode: `./gradlew` + - It will run `clean`, `headless`, `allTests`, `coverage` tasks. +- To run all tests in headful mode: `./gradlew clean allTests coverage` +- To run checkstyle followed by headful tests `./gradlew clean checkStyle allTests coverage` +- Running specific test classes in a specific order + - When troubleshooting test failures, + you might want to run some specific tests in a specific order. + - Create a test suite (to specify the test order): + ```java + package address; + + import org.junit.runner.RunWith; + import org.junit.runners.Suite; + + @RunWith(Suite.class) + @Suite.SuiteClasses({ + /*The tests to run, in the order they should run*/ + address.browser.BrowserManagerTest.class, + guitests.PersonOverviewTest.class + }) + + public class TestsInOrder { + // the class remains empty, + // used only as a holder for the above annotations + } + ``` + - Run the test suite using the gradle command
+ `./gradlew clean headless allTests --tests address.TestsInOrder` + + +### CI Testing +- We run our CI builds on [Travis CI](https://travis-ci.org/HubTurbo/addressbook) +- The current Travis CI set up (also found in `.travis.yml`): + - runs the `./gradlew clean headless allTests headfulTests headlessTests coverage coveralls -i` command. + - At the moment, we do not check the code style. It is up to the contributor to verify his or her coding style locally by running `./gradlew checkStyle`. + - Automatically retries up to 3 times if a task fails diff --git a/gradle.properties b/gradle.properties new file mode 100644 index 000000000000..728342109ca9 --- /dev/null +++ b/gradle.properties @@ -0,0 +1,3 @@ +org.gradle.daemon=true +org.gradle.parallel=false +org.gradle.jvmargs=-XX:MaxPermSize=512m -XX:+CMSClassUnloadingEnabled -XX:+CMSPermGenSweepingEnabled -XX:+HeapDumpOnOutOfMemoryError -Xmx1024m -Dfile.encoding=utf-8 diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar new file mode 100644 index 0000000000000000000000000000000000000000..b5166dad4d90021f6a0b45268c0755719f1d5cd4 GIT binary patch literal 52266 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 z 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 literal 0 HcmV?d00001 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties new file mode 100644 index 000000000000..4d04417b7daf --- /dev/null +++ b/gradle/wrapper/gradle-wrapper.properties @@ -0,0 +1,6 @@ +#Mon Mar 21 13:28:19 SGT 2016 +distributionBase=GRADLE_USER_HOME +distributionPath=wrapper/dists +zipStoreBase=GRADLE_USER_HOME +zipStorePath=wrapper/dists +distributionUrl=https\://services.gradle.org/distributions/gradle-2.12-all.zip diff --git a/gradlew b/gradlew new file mode 100755 index 000000000000..91a7e269e19d --- /dev/null +++ b/gradlew @@ -0,0 +1,164 @@ +#!/usr/bin/env bash + +############################################################################## +## +## Gradle start up script for UN*X +## +############################################################################## + +# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +DEFAULT_JVM_OPTS="" + +APP_NAME="Gradle" +APP_BASE_NAME=`basename "$0"` + +# Use the maximum available, or set MAX_FD != -1 to use that value. +MAX_FD="maximum" + +warn ( ) { + echo "$*" +} + +die ( ) { + echo + echo "$*" + echo + exit 1 +} + +# OS specific support (must be 'true' or 'false'). +cygwin=false +msys=false +darwin=false +case "`uname`" in + CYGWIN* ) + cygwin=true + ;; + Darwin* ) + darwin=true + ;; + MINGW* ) + msys=true + ;; +esac + +# For Cygwin, ensure paths are in UNIX format before anything is touched. +if $cygwin ; then + [ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"` +fi + +# Attempt to set APP_HOME +# Resolve links: $0 may be a link +PRG="$0" +# Need this for relative symlinks. +while [ -h "$PRG" ] ; do + ls=`ls -ld "$PRG"` + link=`expr "$ls" : '.*-> \(.*\)$'` + if expr "$link" : '/.*' > /dev/null; then + PRG="$link" + else + PRG=`dirname "$PRG"`"/$link" + fi +done +SAVED="`pwd`" +cd "`dirname \"$PRG\"`/" >&- +APP_HOME="`pwd -P`" +cd "$SAVED" >&- + +CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar + +# Determine the Java command to use to start the JVM. +if [ -n "$JAVA_HOME" ] ; then + if [ -x "$JAVA_HOME/jre/sh/java" ] ; then + # IBM's JDK on AIX uses strange locations for the executables + JAVACMD="$JAVA_HOME/jre/sh/java" + else + JAVACMD="$JAVA_HOME/bin/java" + fi + if [ ! -x "$JAVACMD" ] ; then + die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." + fi +else + JAVACMD="java" + which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. + +Please set the JAVA_HOME variable in your environment to match the +location of your Java installation." +fi + +# Increase the maximum file descriptors if we can. +if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then + MAX_FD_LIMIT=`ulimit -H -n` + if [ $? -eq 0 ] ; then + if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then + MAX_FD="$MAX_FD_LIMIT" + fi + ulimit -n $MAX_FD + if [ $? -ne 0 ] ; then + warn "Could not set maximum file descriptor limit: $MAX_FD" + fi + else + warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT" + fi +fi + +# For Darwin, add options to specify how the application appears in the dock +if $darwin; then + GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\"" +fi + +# For Cygwin, switch paths to Windows format before running java +if $cygwin ; then + APP_HOME=`cygpath --path --mixed "$APP_HOME"` + CLASSPATH=`cygpath --path --mixed "$CLASSPATH"` + + # We build the pattern for arguments to be converted via cygpath + ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null` + SEP="" + for dir in $ROOTDIRSRAW ; do + ROOTDIRS="$ROOTDIRS$SEP$dir" + SEP="|" + done + OURCYGPATTERN="(^($ROOTDIRS))" + # Add a user-defined pattern to the cygpath arguments + if [ "$GRADLE_CYGPATTERN" != "" ] ; then + OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)" + fi + # Now convert the arguments - kludge to limit ourselves to /bin/sh + i=0 + for arg in "$@" ; do + CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -` + CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option + + if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition + eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"` + else + eval `echo args$i`="\"$arg\"" + fi + i=$((i+1)) + done + case $i in + (0) set -- ;; + (1) set -- "$args0" ;; + (2) set -- "$args0" "$args1" ;; + (3) set -- "$args0" "$args1" "$args2" ;; + (4) set -- "$args0" "$args1" "$args2" "$args3" ;; + (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;; + (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;; + (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;; + (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;; + (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;; + esac +fi + +# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules +function splitJvmOpts() { + JVM_OPTS=("$@") +} +eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS +JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME" + +exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@" diff --git a/gradlew.bat b/gradlew.bat new file mode 100644 index 000000000000..8a0b282aa688 --- /dev/null +++ b/gradlew.bat @@ -0,0 +1,90 @@ +@if "%DEBUG%" == "" @echo off +@rem ########################################################################## +@rem +@rem Gradle startup script for Windows +@rem +@rem ########################################################################## + +@rem Set local scope for the variables with windows NT shell +if "%OS%"=="Windows_NT" setlocal + +@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script. +set DEFAULT_JVM_OPTS= + +set DIRNAME=%~dp0 +if "%DIRNAME%" == "" set DIRNAME=. +set APP_BASE_NAME=%~n0 +set APP_HOME=%DIRNAME% + +@rem Find java.exe +if defined JAVA_HOME goto findJavaFromJavaHome + +set JAVA_EXE=java.exe +%JAVA_EXE% -version >NUL 2>&1 +if "%ERRORLEVEL%" == "0" goto init + +echo. +echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH. +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:findJavaFromJavaHome +set JAVA_HOME=%JAVA_HOME:"=% +set JAVA_EXE=%JAVA_HOME%/bin/java.exe + +if exist "%JAVA_EXE%" goto init + +echo. +echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME% +echo. +echo Please set the JAVA_HOME variable in your environment to match the +echo location of your Java installation. + +goto fail + +:init +@rem Get command-line arguments, handling Windowz variants + +if not "%OS%" == "Windows_NT" goto win9xME_args +if "%@eval[2+2]" == "4" goto 4NT_args + +:win9xME_args +@rem Slurp the command line arguments. +set CMD_LINE_ARGS= +set _SKIP=2 + +:win9xME_args_slurp +if "x%~1" == "x" goto execute + +set CMD_LINE_ARGS=%* +goto execute + +:4NT_args +@rem Get arguments from the 4NT Shell from JP Software +set CMD_LINE_ARGS=%$ + +:execute +@rem Setup the command line + +set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar + +@rem Execute Gradle +"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS% + +:end +@rem End local scope for the variables with windows NT shell +if "%ERRORLEVEL%"=="0" goto mainEnd + +:fail +rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of +rem the _cmd.exe /c_ return code! +if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1 +exit /b 1 + +:mainEnd +if "%OS%"=="Windows_NT" endlocal + +:omega diff --git a/src/main/java/address/MainApp.java b/src/main/java/address/MainApp.java new file mode 100644 index 000000000000..f76f175252c4 --- /dev/null +++ b/src/main/java/address/MainApp.java @@ -0,0 +1,109 @@ +package address; + +import address.model.ModelManager; +import address.model.UserPrefs; +import address.storage.StorageManager; +import address.ui.Ui; +import address.util.AppLogger; +import address.util.Config; +import address.util.DependencyChecker; +import address.util.LoggerManager; +import javafx.application.Application; +import javafx.application.Platform; +import javafx.stage.Stage; + +import java.util.Map; + +/** + * The main entry point to the application. + */ +public class MainApp extends Application { + private static final AppLogger logger = LoggerManager.getLogger(MainApp.class); + + private static final int VERSION_MAJOR = 1; + private static final int VERSION_MINOR = 6; + private static final int VERSION_PATCH = 1; + private static final boolean VERSION_EARLY_ACCESS = true; + + public static final commons.Version VERSION = new commons.Version( + VERSION_MAJOR, VERSION_MINOR, VERSION_PATCH, VERSION_EARLY_ACCESS); + + /** + * Minimum Java Version Required + * + * Due to usage of ControlsFX 8.40.10, requires minimum Java version of 1.8.0_60. + */ + public static final String REQUIRED_JAVA_VERSION = "1.8.0_60"; // update docs if this is changed + + protected StorageManager storageManager; + protected ModelManager modelManager; + + protected Ui ui; + protected Config config; + protected UserPrefs userPrefs; + + public MainApp() {} + + @Override + public void init() throws Exception { + logger.info("Initializing app ..."); + super.init(); + new DependencyChecker(REQUIRED_JAVA_VERSION, this::quit).verify(); + Map applicationParameters = getParameters().getNamed(); + config = initConfig(applicationParameters.get("config")); + userPrefs = initPrefs(config); + initComponents(config, userPrefs); + } + + protected Config initConfig(String configFilePath) { + return StorageManager.getConfig(configFilePath); + } + + protected UserPrefs initPrefs(Config config) { + return StorageManager.getUserPrefs(config.getPrefsFileLocation()); + } + + private void initComponents(Config config, UserPrefs userPrefs) { + LoggerManager.init(config); + + modelManager = initModelManager(config); + storageManager = initStorageManager(modelManager, config, userPrefs); + ui = initUi(config, modelManager); + } + + protected Ui initUi(Config config, ModelManager modelManager) { + return new Ui(this, modelManager, config, userPrefs); + } + + protected StorageManager initStorageManager(ModelManager modelManager, Config config, UserPrefs userPrefs) { + return new StorageManager(modelManager::resetData, modelManager::getDefaultAddressBook, config, userPrefs); + } + + protected ModelManager initModelManager(Config config) { + return new ModelManager(config); + } + + @Override + public void start(Stage primaryStage) { + logger.info("Starting application: {}", MainApp.VERSION); + ui.start(primaryStage); + storageManager.start(); + } + + @Override + public void stop() { + logger.info("Stopping application."); + ui.stop(); + storageManager.savePrefsToFile(userPrefs); + quit(); + } + + private void quit() { + Platform.exit(); + System.exit(0); + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/src/main/java/address/browser/BrowserManager.java b/src/main/java/address/browser/BrowserManager.java new file mode 100644 index 000000000000..b96fa4f2d0db --- /dev/null +++ b/src/main/java/address/browser/BrowserManager.java @@ -0,0 +1,88 @@ +package address.browser; + +import address.model.datatypes.person.ReadOnlyPerson; +import address.util.AppLogger; +import address.util.LoggerManager; +import commons.FxViewUtil; +import commons.UrlUtil; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.beans.value.ChangeListener; +import javafx.scene.Node; +import javafx.scene.web.WebView; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Manages the AddressBook browser. + * To begin using this class: call start(). + */ +public class BrowserManager { + + private static final String GITHUB_ROOT_URL = "https://github.com/"; + private static final String INVALID_GITHUB_USERNAME_MESSAGE = "Unparsable GitHub Username."; + private static AppLogger logger = LoggerManager.getLogger(BrowserManager.class); + private WebView browser; + private StringProperty selectedPersonUsername; + private ChangeListener listener; + + public BrowserManager() { + this.selectedPersonUsername = new SimpleStringProperty(); + initListener(); + } + + /** + * Initializes the listener for changes in selected person GitHub username. + */ + private void initListener() { + listener = (observable, oldValue, newValue) -> { + try { + URL url = new URL(GITHUB_ROOT_URL + newValue); + if (!UrlUtil.compareBaseUrls(new URL(browser.getEngine().getLocation()), url)) { + browser.getEngine().load(url.toExternalForm()); + } + } catch (MalformedURLException e) { + logger.warn("Malformed URL obtained, not attempting to load."); + if (!newValue.equals("")) { + browser.getEngine().loadContent(INVALID_GITHUB_USERNAME_MESSAGE); + } + } + }; + } + + /** + * Starts the browser manager. + * Precondition: FX runtime is initialized and in FX application thread. + */ + public void start() { + logger.info("Initializing browser"); + browser = new WebView(); //Webview need to be initialize after FX runtime is initialized + } + + /** + * Loads the person's profile page to the browser. + */ + public synchronized void loadProfilePage(ReadOnlyPerson person) { + + selectedPersonUsername.removeListener(listener); + + browser.getEngine().load(person.profilePageUrl().toExternalForm()); + + selectedPersonUsername.unbind(); + selectedPersonUsername.bind(person.githubUsernameProperty()); + selectedPersonUsername.addListener(listener); + } + + /** + * Frees resources allocated to the browser. + */ + public void freeBrowserResources() { + browser = null; + } + + public Node getBrowserView() { + FxViewUtil.applyAnchorBoundaryParameters(browser, 0.0, 0.0, 0.0, 0.0); + return browser; + } +} diff --git a/src/main/java/address/controller/HelpController.java b/src/main/java/address/controller/HelpController.java new file mode 100644 index 000000000000..2881ee4e6f85 --- /dev/null +++ b/src/main/java/address/controller/HelpController.java @@ -0,0 +1,28 @@ +package address.controller; + +import address.MainApp; +import commons.FxViewUtil; +import javafx.fxml.FXML; +import javafx.scene.layout.AnchorPane; +import javafx.scene.web.WebView; + +/** + * Controller for a help page + */ +public class HelpController { + + @FXML + private AnchorPane mainPane; + + public HelpController() { + + } + + @FXML + public void initialize() { + WebView browser = new WebView(); + browser.getEngine().load(MainApp.class.getResource("/help_html/index.html").toExternalForm()); + FxViewUtil.applyAnchorBoundaryParameters(browser, 0.0, 0.0, 0.0, 0.0); + mainPane.getChildren().add(browser); + } +} diff --git a/src/main/java/address/controller/MainController.java b/src/main/java/address/controller/MainController.java new file mode 100644 index 000000000000..a1c86dbbd95b --- /dev/null +++ b/src/main/java/address/controller/MainController.java @@ -0,0 +1,385 @@ +package address.controller; + +import address.MainApp; +import address.browser.BrowserManager; +import address.events.controller.MinimizeAppRequestEvent; +import address.events.controller.ResizeAppRequestEvent; +import address.events.hotkey.KeyBindingEvent; +import address.events.storage.FileOpeningExceptionEvent; +import address.events.storage.FileSavingExceptionEvent; +import address.model.ModelManager; +import address.model.UserPrefs; +import address.model.datatypes.person.ReadOnlyPerson; +import address.model.datatypes.tag.Tag; +import address.util.AppLogger; +import address.util.Config; +import address.util.LoggerManager; +import address.util.collections.UnmodifiableObservableList; +import com.google.common.eventbus.Subscribe; +import commons.FxViewUtil; +import javafx.application.Platform; +import javafx.collections.ObservableList; +import javafx.event.EventType; +import javafx.fxml.FXMLLoader; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.control.Alert; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.SplitPane; +import javafx.scene.image.Image; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.GridPane; +import javafx.scene.layout.VBox; +import javafx.stage.Modality; +import javafx.stage.Stage; + +import java.io.File; +import java.io.IOException; + +/** + * The controller that creates the other controllers + */ +public class MainController extends UiController{ + public static final String DIALOG_TITLE_TAG_LIST = "List of Tags"; + private static final AppLogger logger = LoggerManager.getLogger(MainController.class); + private static final String FXML_HELP = "/view/Help.fxml"; + private static final String FXML_STATUS_BAR_FOOTER = "/view/StatusBarFooter.fxml"; + private static final String FXML_PERSON_LIST_PANEL = "/view/PersonListPanel.fxml"; + private static final String FXML_TAG_LIST = "/view/TagList.fxml"; + private static final String FXML_ROOT_LAYOUT = "/view/RootLayout.fxml"; + private static final String ICON_APPLICATION = "/images/address_book_32.png"; + private static final String ICON_HELP = "/images/help_icon.png"; + private static final int MIN_HEIGHT = 600; + private static final int MIN_WIDTH = 450; + + private static final String PERSON_LIST_PANEL_FIELD_ID = "#personListPanel"; + private static final String HEADER_STATUSBAR_PLACEHOLDER_FIELD_ID = "#headerStatusbarPlaceholder"; + private static final String FOOTER_STATUSBAR_PLACEHOLDER_FIELD_ID = "#footerStatusbarPlaceholder"; + private static final String PERSON_WEBPAGE_PLACE_HOLDER_FIELD_ID = "#personWebpage"; + + private Stage primaryStage; + private VBox rootLayout; + + private ModelManager modelManager; + private BrowserManager browserManager; + private MainApp mainApp; + private Config config; + private UserPrefs prefs; + + private StatusBarHeaderController statusBarHeaderController; + private StatusBarFooterController statusBarFooterController; + + private UnmodifiableObservableList personList; + + /** + * Constructor for mainController + * + * @param mainApp + * @param modelManager + * @param config should have appTitle and updateInterval set + */ + public MainController(MainApp mainApp, ModelManager modelManager, Config config, UserPrefs prefs) { + super(); + this.mainApp = mainApp; + this.modelManager = modelManager; + this.config = config; + this.prefs = prefs; + this.personList = modelManager.getPersonsAsReadOnlyObservableList(); + this.browserManager = new BrowserManager(); + } + + public void start(Stage primaryStage) { + logger.info("Starting main controller."); + this.primaryStage = primaryStage; + this.browserManager.start(); + primaryStage.setTitle(config.getAppTitle()); + + // Set the application icon. + this.primaryStage.getIcons().add(getImage(ICON_APPLICATION)); + + initRootLayout(); + showPersonListPanel(); + showPersonWebPage(); + showFooterStatusBar(); + showHeaderStatusBar(); + } + + /** + * Initializes the root layout and tries to load the last opened + * person file. + */ + public void initRootLayout() { + logger.debug("Initializing root layout."); + final String fxmlResourcePath = FXML_ROOT_LAYOUT; + // Load root layout from fxml file. + FXMLLoader loader = loadFxml(fxmlResourcePath); + rootLayout = (VBox) loadLoader(loader, "Error initializing root layout"); + + // Show the scene containing the root layout. + Scene scene = new Scene(rootLayout); + scene.setOnKeyPressed(event -> raisePotentialEvent(new KeyBindingEvent(event))); + primaryStage.setScene(scene); + setMinSize(); + setDefaultSize(); + + // Give the rootController access to the main controller and modelManager + RootLayoutController rootController = loader.getController(); + rootController.setConnections(mainApp, this, modelManager); + rootController.setAccelerators(); + + primaryStage.show(); + } + + public StatusBarHeaderController getStatusBarHeaderController() { + return statusBarHeaderController; + } + + /** + * Shows the person list panel inside the root layout. + */ + public void showPersonListPanel() { + logger.debug("Loading person list panel."); + final String fxmlResourcePath = FXML_PERSON_LIST_PANEL; + // Load person overview. + FXMLLoader loader = loadFxml(fxmlResourcePath); + VBox personListPanel = (VBox) loadLoader(loader, "Error loading person list panel"); + AnchorPane pane = (AnchorPane) rootLayout.lookup(PERSON_LIST_PANEL_FIELD_ID); + SplitPane.setResizableWithParent(pane, false); + // Give the personListPanelController access to the main app and modelManager. + PersonListPanelController personListPanelController = loader.getController(); + personListPanelController.setConnections(this, modelManager, personList); + + pane.getChildren().add(personListPanel); + } + + private void showHeaderStatusBar() { + statusBarHeaderController = new StatusBarHeaderController(this); + AnchorPane sbPlaceHolder = (AnchorPane) rootLayout.lookup(HEADER_STATUSBAR_PLACEHOLDER_FIELD_ID); + + assert sbPlaceHolder != null : "headerStatusbarPlaceHolder node not found in rootLayout"; + + FxViewUtil.applyAnchorBoundaryParameters(statusBarHeaderController.getHeaderStatusBarView(), + 0.0, 0.0, 0.0, 0.0); + sbPlaceHolder.getChildren().add(statusBarHeaderController.getHeaderStatusBarView()); + } + + private void showFooterStatusBar() { + logger.debug("Loading footer status bar."); + final String fxmlResourcePath = FXML_STATUS_BAR_FOOTER; + FXMLLoader loader = loadFxml(fxmlResourcePath); + GridPane gridPane = (GridPane) loadLoader(loader, "Error Loading footer status bar"); + gridPane.getStyleClass().add("grid-pane"); + statusBarFooterController = loader.getController(); + statusBarFooterController.init(config.getAddressBookName()); + AnchorPane placeHolder = (AnchorPane) rootLayout.lookup(FOOTER_STATUSBAR_PLACEHOLDER_FIELD_ID); + FxViewUtil.applyAnchorBoundaryParameters(gridPane, 0.0, 0.0, 0.0, 0.0); + placeHolder.getChildren().add(gridPane); + } + + public void showPersonWebPage() { + AnchorPane pane = (AnchorPane) rootLayout.lookup(PERSON_WEBPAGE_PLACE_HOLDER_FIELD_ID); + disableKeyboardShortcutOnNode(pane); + pane.getChildren().add(browserManager.getBrowserView()); + } + + private Node loadLoader(FXMLLoader loader, String errorMsg) { + try { + return loader.load(); + } catch (IOException e) { + logger.fatal(errorMsg + ": {}", e); + showFatalErrorDialogAndShutdown("FXML Load Error", errorMsg, + "IOException when trying to load ", loader.getLocation().toExternalForm()); + return null; + } + } + + private FXMLLoader loadFxml(String fxmlResourcePath) { + FXMLLoader loader = new FXMLLoader(); + loader.setLocation(MainApp.class.getResource(fxmlResourcePath)); + return loader; + } + + private Stage loadDialogStage(String value, Stage primaryStage, Scene scene) { + Stage dialogStage = new Stage(); + dialogStage.setTitle(value); + dialogStage.initModality(Modality.WINDOW_MODAL); + dialogStage.initOwner(primaryStage); + dialogStage.setScene(scene); + return dialogStage; + } + + public void showTagList(ObservableList tags) { + logger.debug("Loading tag list."); + final String fxmlResourcePath = FXML_TAG_LIST; + // Load the fxml file and create a new stage for the popup dialog. + FXMLLoader loader = loadFxml(fxmlResourcePath); + AnchorPane page = (AnchorPane) loadLoader(loader, "Error loading tag list view"); + + Scene scene = new Scene(page); + Stage dialogStage = loadDialogStage(DIALOG_TITLE_TAG_LIST, primaryStage, scene); + + // Set the tag into the controller. + TagListController tagListController = loader.getController(); + tagListController.setMainController(this); + tagListController.setModelManager(modelManager); + tagListController.setTags(tags); + tagListController.setStage(dialogStage); + + // Show the dialog and wait until the user closes it + dialogStage.showAndWait(); + } + + /** + * Opens a dialog to show the help page + */ + public void showHelpPage() { + logger.debug("Loading help page."); + final String fxmlResourcePath = FXML_HELP; + // Load the fxml file and create a new stage for the popup dialog. + FXMLLoader loader = loadFxml(fxmlResourcePath); + AnchorPane page = (AnchorPane) loadLoader(loader, "Error loading help page"); + + Scene scene = new Scene(page); + Stage dialogStage = loadDialogStage("Help", null, scene); + dialogStage.getIcons().add(getImage(ICON_HELP)); + dialogStage.setMaximized(true); + // Show the dialog and wait until the user closes it + dialogStage.showAndWait(); + } + + /** + * Returns the main stage. + * @return + */ + public Stage getPrimaryStage() { + return primaryStage; + } + + @Subscribe + private void handleFileOpeningExceptionEvent(FileOpeningExceptionEvent foee) { + showFileOperationAlertAndWait("Could not load data", "Could not load data from file", foee.file, + foee.exception); + } + + @Subscribe + private void handleFileSavingExceptionEvent(FileSavingExceptionEvent fsee) { + showFileOperationAlertAndWait("Could not save data", "Could not save data to file", fsee.file, fsee.exception); + } + + private void showFileOperationAlertAndWait(String description, String details, File file, Throwable cause) { + final String content = details + ":\n" + (file == null ? "none" : file.getPath()) + "\n\nDetails:\n======\n" + + cause.toString(); + showAlertDialogAndWait(AlertType.ERROR, "File Op Error", description, content); + } + + private Image getImage(String imagePath) { + return new Image(MainApp.class.getResourceAsStream(imagePath)); + } + + public void showAlertDialogAndWait(AlertType type, String title, String headerText, String contentText) { + showAlertDialogAndWait(primaryStage, type, title, headerText, contentText); + } + + public static void showAlertDialogAndWait(Stage owner, AlertType type, String title, String headerText, + String contentText) { + final Alert alert = new Alert(type); + alert.getDialogPane().getStylesheets().add("view/DarkTheme.css"); + alert.initOwner(owner); + alert.setTitle(title); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + + alert.showAndWait(); + } + + /** + * Releases resources to ensure successful application termination. + */ + public void releaseResourcesForAppTermination(){ + browserManager.freeBrowserResources(); + } + + public void loadGithubProfilePage(ReadOnlyPerson person){ + browserManager.loadProfilePage(person); + } + + private void disableKeyboardShortcutOnNode(Node pane) { + pane.addEventHandler(EventType.ROOT, event -> event.consume()); + } + + @Subscribe + private void handleResizeAppRequestEvent(ResizeAppRequestEvent event){ + logger.debug("Handling the resize app window request"); + Platform.runLater(this::handleResizeRequest); + } + + @Subscribe + private void handleMinimizeAppRequestEvent(MinimizeAppRequestEvent event){ + logger.debug("Handling the minimize app window request"); + Platform.runLater(this::minimizeWindow); + } + + protected void setDefaultSize() { + primaryStage.setHeight(prefs.getGuiSettings().getWindowHeight()); + primaryStage.setWidth(prefs.getGuiSettings().getWindowWidth()); + if (prefs.getGuiSettings().getWindowCoordinates() != null) { + primaryStage.setX(prefs.getGuiSettings().getWindowCoordinates().getX()); + primaryStage.setY(prefs.getGuiSettings().getWindowCoordinates().getY()); + } + } + + private void setMinSize() { + primaryStage.setMinHeight(MIN_HEIGHT); + primaryStage.setMinWidth(MIN_WIDTH); + } + + private void minimizeWindow() { + logger.info("Minimizing App"); + logger.debug("PrimaryStage title: " + primaryStage.getTitle()); + primaryStage.setIconified(true); + primaryStage.setMaximized(false); + } + + private void handleResizeRequest() { + logger.info("Handling resize request."); + if (primaryStage.isIconified()) { + logger.debug("Cannot resize as window is iconified, attempting to show window instead."); + primaryStage.setIconified(false); + } else { + resizeWindow(); + } + } + + private void resizeWindow() { + logger.info("Resizing window"); + // specially handle since stage operations on Mac seem to not be working as intended + if (commons.OsDetector.isOnMac()) { + // refresh stage so that resizing effects (apart from the first resize after iconify-ing) are applied + // however, this will cause minor flinching in window visibility + primaryStage.hide(); // hide has to be called before setMaximized, + // or first resize attempt after iconify-ing will resize twice + primaryStage.show(); + + // on Mac, setMaximized seems to work like "setResize" + // isMaximized also does not seem to return the correct value + primaryStage.setMaximized(true); + } else { + primaryStage.setMaximized(!primaryStage.isMaximized()); + } + + logger.debug("After: Stage width: " + primaryStage.getWidth() + " Stage Height: " + primaryStage.getHeight()); + } + + public void stop() { + getPrimaryStage().hide(); + releaseResourcesForAppTermination(); + } + + private void showFatalErrorDialogAndShutdown(String title, String headerText, String contentText, + String errorLocation) { + showAlertDialogAndWait(AlertType.ERROR, title, headerText, + contentText + errorLocation); + Platform.exit(); + System.exit(1); + } +} diff --git a/src/main/java/address/controller/PersonCardController.java b/src/main/java/address/controller/PersonCardController.java new file mode 100644 index 000000000000..a26aa6e8f5a4 --- /dev/null +++ b/src/main/java/address/controller/PersonCardController.java @@ -0,0 +1,113 @@ +package address.controller; + +import address.model.datatypes.person.ReadOnlyPerson; +import javafx.beans.binding.StringBinding; +import javafx.beans.property.SimpleStringProperty; +import javafx.beans.property.StringProperty; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; +import javafx.scene.control.Label; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.HBox; + +import java.io.IOException; + +public class PersonCardController extends UiController { + + @FXML + private HBox cardPane; + @FXML + private Label firstName; + @FXML + private Label lastName; + @FXML + private Label address; + @FXML + private Label birthday; + @FXML + private Label tags; + + private ReadOnlyPerson person; + private StringProperty idTooltipString = new SimpleStringProperty(""); + + public PersonCardController(ReadOnlyPerson person) { + this.person = person; + + FXMLLoader fxmlLoader = new FXMLLoader(getClass().getResource("/view/PersonListCard.fxml")); + fxmlLoader.setController(this); + try { + fxmlLoader.load(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @FXML + public void initialize() { + bindDisplayedPersonData(); + initIdTooltip(); + } + + private void bindDisplayedPersonData() { + + firstName.textProperty().bind(person.firstNameProperty()); + lastName.textProperty().bind(person.lastNameProperty()); + + address.textProperty().bind(new StringBinding() { + { + bind(person.streetProperty()); + bind(person.postalCodeProperty()); + bind(person.cityProperty()); + } + @Override + protected String computeValue() { + return getAddressString(person.getStreet(), person.getCity(), person.getPostalCode()); + } + }); + birthday.textProperty().bind(new StringBinding() { + { + bind(person.birthdayProperty()); //Bind property at instance initializer + } + @Override + protected String computeValue() { + return person.birthdayString().length() > 0 ? "DOB: " + person.birthdayString() : ""; + } + }); + tags.textProperty().bind(new StringBinding() { + { + bind(person.getObservableTagList()); //Bind property at instance initializer + } + @Override + protected String computeValue() { + return person.tagsString().equals("") ? "" : " [ " + person.tagsString() + " ]"; + } + }); + + } + + private void initIdTooltip() { + Tooltip tp = new Tooltip(); + tp.textProperty().bind(idTooltipString); + firstName.setTooltip(tp); + lastName.setTooltip(tp); + idTooltipString.set(person.idString()); + } + + public HBox getLayout() { + return cardPane; + } + + public static String getAddressString(String street, String city, String postalCode) { + StringBuilder sb = new StringBuilder(); + if (street.length() > 0) { + sb.append(street).append(System.lineSeparator()); + } + if (city.length() > 0) { + sb.append(city).append(System.lineSeparator()); + } + if (postalCode.length() > 0) { + sb.append(postalCode); + } + return sb.toString(); + } +} diff --git a/src/main/java/address/controller/PersonListPanelController.java b/src/main/java/address/controller/PersonListPanelController.java new file mode 100644 index 000000000000..21b876a32d5d --- /dev/null +++ b/src/main/java/address/controller/PersonListPanelController.java @@ -0,0 +1,159 @@ +package address.controller; + +import address.events.controller.JumpToListRequestEvent; +import address.events.parser.FilterCommittedEvent; +import address.model.ModelManager; +import address.model.datatypes.person.ReadOnlyPerson; +import address.parser.expr.PredExpr; +import address.parser.qualifier.TrueQualifier; +import address.ui.PersonListViewCell; +import address.util.AppLogger; +import address.util.LoggerManager; +import address.util.collections.FilteredList; +import com.google.common.eventbus.Subscribe; +import javafx.application.Platform; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.ListView; +import javafx.scene.control.SelectionMode; + +import java.util.List; +import java.util.Objects; + +/** + * Dialog to view the list of persons and their details + * + * setConnections should be set before showing stage + */ +public class PersonListPanelController extends UiController { + private static AppLogger logger = LoggerManager.getLogger(PersonListPanelController.class); + + @FXML + private ListView personListView; + + private MainController mainController; + private ModelManager modelManager; + private FilteredList filteredPersonList; + + + public PersonListPanelController() { + super(); + } + + public void setConnections(MainController mainController, ModelManager modelManager, + ObservableList personList) { + this.mainController = mainController; + this.modelManager = modelManager; + filteredPersonList = new FilteredList<>(personList, new PredExpr(new TrueQualifier())::satisfies); + + personListView.setItems(filteredPersonList); + personListView.setCellFactory(listView -> new PersonListViewCell()); + loadGithubProfilePageWhenPersonIsSelected(mainController); + setupListviewSelectionModelSettings(); + } + + private void setupListviewSelectionModelSettings() { + personListView.getSelectionModel().setSelectionMode(SelectionMode.MULTIPLE); + personListView.getItems().addListener((ListChangeListener) c -> { + while(c.next()) { + if (c.wasRemoved()) { + ObservableList currentIndices = personListView.getSelectionModel().getSelectedIndices(); + if (currentIndices.size() > 1) { + personListView.getSelectionModel().clearAndSelect(currentIndices.get(0)); + } + } + } + }); + } + + @Subscribe + private void handleFilterCommittedEvent(FilterCommittedEvent fce) { + filteredPersonList.setPredicate(fce.filterExpression::satisfies); + } + + private void loadGithubProfilePageWhenPersonIsSelected(MainController mainController) { + personListView.getSelectionModel().selectedItemProperty().addListener((observable, oldValue, newValue) -> { + if (newValue != null) { + logger.debug("Person in list view clicked. Loading GitHub profile page: '{}'", newValue); + mainController.loadGithubProfilePage(newValue); + } + }); + } + + + /** + * Initializes the controller class. This method is automatically called + * after the fxml file has been loaded. + */ + @FXML + private void initialize() { + + } + + /** + * Informs user if selection is invalid + * @return true if selection is valid, false otherwise + */ + private boolean checkAndHandleInvalidSelection() { + final List selected = personListView.getSelectionModel().getSelectedItems(); + if (selected.isEmpty() || selected.stream().anyMatch(Objects::isNull)) { + showNoValidSelectionAlert(); + return false; + } + return true; + } + + private String collateNames(List list) { + StringBuilder sb = new StringBuilder(); + list.stream().forEach(p -> sb.append(p.fullName() + ", ")); + return sb.toString().substring(0, sb.toString().length() - 2); + } + + @Subscribe + private void handleJumpToListRequestEvent(JumpToListRequestEvent event) { + Platform.runLater(() -> { + jumpToListItem(event.targetIndex); + }); + } + + /** + * Jumps the Nth item of the list if it exists. No action if the Nth item does not exist. + * Jumps to the bottom if {@code targetIndex = -1} + * + * @param targetIndex starts from 1. To jump to 1st item, targetIndex should be 1. + * To jump to bottom, should be -1. + */ + + private void jumpToListItem(int targetIndex) { + int listSize = personListView.getItems().size(); + if (listSize < targetIndex) { + return; + } + int indexOfItem; + if (targetIndex == -1) { // if the target is the bottom of the list + indexOfItem = listSize - 1; + } else { + indexOfItem = targetIndex - 1; //to account for list indexes starting from 0 + } + + selectItem(indexOfItem); + } + + private void showNoValidSelectionAlert() { + mainController.showAlertDialogAndWait(AlertType.WARNING, + "Invalid Selection", "No Person Selected", "Please select a person in the list."); + } + + /** + * Selects the item in the list and scrolls to it if it is out of view. + * @param indexOfItem + */ + private void selectItem(int indexOfItem) { + personListView.getSelectionModel().clearAndSelect(indexOfItem); + personListView.getFocusModel().focus(indexOfItem); + personListView.requestFocus(); + personListView.scrollTo(indexOfItem); + } +} diff --git a/src/main/java/address/controller/RootLayoutController.java b/src/main/java/address/controller/RootLayoutController.java new file mode 100644 index 000000000000..6cd9df94d414 --- /dev/null +++ b/src/main/java/address/controller/RootLayoutController.java @@ -0,0 +1,106 @@ +package address.controller; + +import address.MainApp; +import address.events.parser.FilterCommittedEvent; +import address.model.ModelManager; +import address.parser.Command; +import address.parser.CommandParser; +import address.parser.ParseException; +import address.parser.Parser; +import address.parser.expr.Expr; +import address.parser.expr.PredExpr; +import address.util.AppLogger; +import address.util.LoggerManager; +import javafx.fxml.FXML; +import javafx.scene.control.Alert.AlertType; +import javafx.scene.control.MenuItem; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCombination; + +/** + * The controller for the root layout. The root layout provides the basic + * application layout containing a menu bar and space where other JavaFX + * elements can be placed. + */ +public class RootLayoutController extends UiController { + private static AppLogger logger = LoggerManager.getLogger(RootLayoutController.class); + + private MainController mainController; + private ModelManager modelManager; + private MainApp mainApp; + + private Parser parser; + + @FXML + private MenuItem helpMenuItem; + + @FXML + private TextField filterField; + + public RootLayoutController() { + super(); + parser = new Parser(); + } + + public void setConnections(MainApp mainApp, MainController mainController, ModelManager modelManager) { + this.mainController = mainController; + this.modelManager = modelManager; + this.mainApp = mainApp; + } + + public void setAccelerators() { + helpMenuItem.setAccelerator(KeyCombination.valueOf("F1")); + } + + @FXML + private void handleFilterChanged() { + + if (CommandParser.isCommandInput(filterField.getText())) { + Command cmd = CommandParser.parse(filterField.getText()); + cmd.execute(modelManager); + return; + } + + Expr filterExpression; + try { + filterExpression = parser.parse(filterField.getText()); + if (filterField.getStyleClass().contains("error")) filterField.getStyleClass().remove("error"); + } catch (ParseException e) { + logger.debug("Invalid filter found: {}", e); + filterExpression = PredExpr.TRUE; + if (!filterField.getStyleClass().contains("error")) filterField.getStyleClass().add("error"); + } + + raise(new FilterCommittedEvent(filterExpression)); + } + + @FXML + private void handleHelp() { + logger.debug("Showing help page about the application."); + mainController.showHelpPage(); + } + + /** + * Opens an about dialog. + */ + @FXML + private void handleAbout() { + logger.debug("Showing information about the application."); + mainController.showAlertDialogAndWait(AlertType.INFORMATION, "AddressApp", "About", + "Version " + MainApp.VERSION.toString() + "\nSome code adapted from http://code.makery.ch"); + } + + /** + * Closes the application. + */ + @FXML + private void handleExit() { + mainApp.stop(); + } + + @FXML + private void handleShowTags() { + logger.debug("Attempting to show tag list."); + mainController.showTagList(modelManager.getTagsAsReadOnlyObservableList()); + } +} diff --git a/src/main/java/address/controller/StatusBarFooterController.java b/src/main/java/address/controller/StatusBarFooterController.java new file mode 100644 index 000000000000..3cff97c0ed48 --- /dev/null +++ b/src/main/java/address/controller/StatusBarFooterController.java @@ -0,0 +1,60 @@ +package address.controller; + +import commons.FxViewUtil; +import javafx.fxml.FXML; +import javafx.scene.control.Tooltip; +import javafx.scene.layout.AnchorPane; +import org.controlsfx.control.StatusBar; + +/** + * A controller for the status bar that is displayed at the footer of the application. + */ +public class StatusBarFooterController extends UiController { + + private static StatusBar syncStatusBar; + private static StatusBar saveLocStatusBar; + + @FXML + private AnchorPane saveLocStatusBarPane; + @FXML + private AnchorPane syncStatusBarPane; + + public StatusBarFooterController() { + super(); + } + + /** + * Initializes the status bar + * @param addressBookName name of the active address book + */ + public void init(String addressBookName) { + initStatusBar(); + initAddressBookLabel(addressBookName); + } + + private void initAddressBookLabel(String addressBookName) { + updateSaveLocationDisplay(addressBookName); + setTooltip(saveLocStatusBar); + } + + private void initStatusBar() { + this.syncStatusBar = new StatusBar(); + this.saveLocStatusBar = new StatusBar(); + this.syncStatusBar.setText(""); + this.saveLocStatusBar.setText(""); + FxViewUtil.applyAnchorBoundaryParameters(syncStatusBar, 0.0, 0.0, 0.0, 0.0); + FxViewUtil.applyAnchorBoundaryParameters(saveLocStatusBar, 0.0, 0.0, 0.0, 0.0); + syncStatusBarPane.getChildren().add(syncStatusBar); + saveLocStatusBarPane.getChildren().add(saveLocStatusBar); + } + + private void setTooltip(StatusBar statusBar) { + Tooltip tp = new Tooltip(); + tp.textProperty().bind(statusBar.textProperty()); + statusBar.setTooltip(tp); + } + + private void updateSaveLocationDisplay(String addressBookName) { + saveLocStatusBar.setText(addressBookName); + } +} diff --git a/src/main/java/address/controller/StatusBarHeaderController.java b/src/main/java/address/controller/StatusBarHeaderController.java new file mode 100644 index 000000000000..00bb13ae0fde --- /dev/null +++ b/src/main/java/address/controller/StatusBarHeaderController.java @@ -0,0 +1,29 @@ +package address.controller; + +import org.controlsfx.control.StatusBar; + +/** + * A controller for the status bar that is displayed at the header of the application. + */ +public class StatusBarHeaderController extends UiController{ + + public static final String HEADER_STATUS_BAR_ID = "headerStatusBar"; + private static final String STATUS_BAR_STYLE_SHEET = "status-bar-with-border"; + private StatusBar headerStatusBar; + + public StatusBarHeaderController(MainController mainController) { + headerStatusBar = new StatusBar(); + headerStatusBar.setId(HEADER_STATUS_BAR_ID); + headerStatusBar.getStyleClass().removeAll(); + headerStatusBar.getStyleClass().add(STATUS_BAR_STYLE_SHEET); + headerStatusBar.setText(""); + } + + public StatusBar getHeaderStatusBarView() { + return headerStatusBar; + } + + public void postMessage(String message) { + headerStatusBar.setText(message); + } +} diff --git a/src/main/java/address/controller/TagCardController.java b/src/main/java/address/controller/TagCardController.java new file mode 100644 index 000000000000..6769f44d1262 --- /dev/null +++ b/src/main/java/address/controller/TagCardController.java @@ -0,0 +1,52 @@ +package address.controller; + +import address.model.datatypes.tag.Tag; +import javafx.fxml.FXML; +import javafx.fxml.FXMLLoader; + +import javafx.scene.control.Label; +import javafx.scene.layout.VBox; + +import java.io.IOException; + +/** + * A controller for TagList's card + */ +public class TagCardController extends UiController{ + + private static final String VIEW_TAG_LIST_CARD_FXML = "/view/TagListCard.fxml"; + + @FXML + private VBox box; + @FXML + private Label tagName; + + private Tag tag; + private MainController mainController; + private TagListController tagListController; + + public TagCardController(Tag tag, MainController mainController, TagListController tagListController) { + this.mainController = mainController; + this.tag = tag; + this.tagListController = tagListController; + + FXMLLoader loader = new FXMLLoader(getClass().getResource(VIEW_TAG_LIST_CARD_FXML)); + loader.setController(this); + + try { + loader.load(); + } catch (IOException e) { + throw new RuntimeException(e); + } + } + + @FXML + public void initialize() { + tagName.setText(tag.getName()); + } + + public VBox getLayout() { + return box; + } + +} diff --git a/src/main/java/address/controller/TagListController.java b/src/main/java/address/controller/TagListController.java new file mode 100644 index 000000000000..4b972db2de3b --- /dev/null +++ b/src/main/java/address/controller/TagListController.java @@ -0,0 +1,66 @@ +package address.controller; + +import address.model.ModelManager; +import address.model.datatypes.tag.Tag; +import javafx.collections.ObservableList; +import javafx.fxml.FXML; +import javafx.geometry.Pos; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.input.KeyCode; +import javafx.scene.layout.AnchorPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +/** + * Dialog to show the list of available tags + * + * Stage, tags, mainController and modelManager should be set before showing stage + */ +public class TagListController extends UiController { + Stage stage; + MainController mainController; + ModelManager modelManager; + + @FXML + private AnchorPane tagListMainPane; + @FXML + private ScrollPane tagListScrollPane; + + public void setStage(Stage stage) { + stage.getScene().setOnKeyPressed(e -> { + if (e.getCode() == KeyCode.ESCAPE) { + e.consume(); + stage.close(); + } + }); + this.stage = stage; + } + + public void setTags(ObservableList tagList) { + tagListScrollPane.setContent(getTagsVBox(tagList, mainController)); + } + + public void setMainController(MainController mainController) { + this.mainController = mainController; + } + + public void setModelManager(ModelManager modelManager) { + this.modelManager = modelManager; + } + + public VBox getTagsVBox(ObservableList tagList, MainController mainController) { + VBox vBox = new VBox(); + vBox.setAlignment(Pos.CENTER_LEFT); + + if (tagList.size() == 0) { + vBox.getChildren().add(new Label("No Tags to show")); + return vBox; + } + tagList.forEach(tag -> { + TagCardController tagCardController = new TagCardController(tag, mainController, this); + vBox.getChildren().add(tagCardController.getLayout()); + }); + return vBox; + } +} diff --git a/src/main/java/address/controller/UiController.java b/src/main/java/address/controller/UiController.java new file mode 100644 index 000000000000..c27ef048f785 --- /dev/null +++ b/src/main/java/address/controller/UiController.java @@ -0,0 +1,21 @@ +package address.controller; + +import address.events.BaseEvent; +import address.events.EventManager; + +/** + * Parent class for all controllers. + */ +public class UiController { + public UiController(){ + EventManager.getInstance().registerHandler(this); + } + + protected void raise(BaseEvent event){ + EventManager.getInstance().post(event); + } + + protected void raisePotentialEvent(BaseEvent event) { + EventManager.getInstance().postPotentialEvent(event); + } +} diff --git a/src/main/java/address/events/BaseEvent.java b/src/main/java/address/events/BaseEvent.java new file mode 100644 index 000000000000..db598a44cb85 --- /dev/null +++ b/src/main/java/address/events/BaseEvent.java @@ -0,0 +1,31 @@ +package address.events; + +import address.events.controller.JumpToListRequestEvent; +import address.events.controller.ResizeAppRequestEvent; + +public abstract class BaseEvent { + + /** + * All Events should have a clear unambiguous custom toString message so that feedback message creation + * stays consistent and reusable. + * + * For example the event manager post method will call any posted event's toString and print it in the console. + */ + public abstract String toString(); + + /** + * Returns true if the {@code other} event has the same intended result as this event, + * even if they are two different event objects. This is a weaker form of equality. + * e.g. if both events intended the selection to jump to the 1st element in the list.
+ * The default implementation returns true of both objects are of the same class. + * For example, comparing two distinct {@link ResizeAppRequestEvent} will return + * true (because both intend to maximize the app).
+ * If the events are parameterized, e.g. {@link JumpToListRequestEvent} this method + * should be overriden to compare the parameters. + * @param other + */ + public boolean hasSameIntentionAs(BaseEvent other){ + return other != null + && this.getClass().equals(other.getClass()); + } +} diff --git a/src/main/java/address/events/EventManager.java b/src/main/java/address/events/EventManager.java new file mode 100644 index 000000000000..804674c1fd7b --- /dev/null +++ b/src/main/java/address/events/EventManager.java @@ -0,0 +1,63 @@ +package address.events; + +import address.util.AppLogger; +import address.util.LoggerManager; +import com.google.common.eventbus.EventBus; + +/** + * Manages the event dispatching of the app. + */ +public class EventManager { + private static final AppLogger logger = LoggerManager.getLogger(EventManager.class); + private final EventBus eventBus; + private static EventManager instance; + + public static EventManager getInstance() { + if (instance == null) { + instance = new EventManager(); + } + return instance; + } + + public static void clearSubscribers() { + instance = null; + } + + private EventManager() { + eventBus = new EventBus(); + } + + public EventManager registerHandler(Object handler) { + eventBus.register(handler); + return this; + } + + /** + * Posts an event to the event bus. + * @param event + * @param + * @return + */ + public EventManager post(E event) { + logger.infoEvent(event); + return postEvent(event); + } + + private EventManager postEvent(E event) { + eventBus.post(event); + return this; + } + + /** + * Similar to {@link #post} event, but logs at debug level. + * To be used for less important events. + * @param event + * @param + * @return + */ + public EventManager postPotentialEvent(E event) { + logger.debugEvent(event); + return postEvent(event); + } + +} diff --git a/src/main/java/address/events/controller/JumpToListRequestEvent.java b/src/main/java/address/events/controller/JumpToListRequestEvent.java new file mode 100644 index 000000000000..c834a20f700e --- /dev/null +++ b/src/main/java/address/events/controller/JumpToListRequestEvent.java @@ -0,0 +1,30 @@ +package address.events.controller; + +import address.events.BaseEvent; + +/** + * Indicates a request to jump to the list of persons + */ +public class JumpToListRequestEvent extends BaseEvent { + + public int targetIndex; + + public JumpToListRequestEvent(int targetIndex) { + this.targetIndex = targetIndex; + } + + @Override + public String toString() { + return this.getClass().getSimpleName(); + } + + /** + * Returns true if both events intended the selection to jump to the same item in the list. + */ + @Override + public boolean hasSameIntentionAs(BaseEvent other){ + return (other != null) + && (other instanceof JumpToListRequestEvent) + && ((JumpToListRequestEvent) other).targetIndex == this.targetIndex; + } +} diff --git a/src/main/java/address/events/controller/MinimizeAppRequestEvent.java b/src/main/java/address/events/controller/MinimizeAppRequestEvent.java new file mode 100644 index 000000000000..d43bf83478ae --- /dev/null +++ b/src/main/java/address/events/controller/MinimizeAppRequestEvent.java @@ -0,0 +1,13 @@ +package address.events.controller; + +import address.events.BaseEvent; + +/** + * Represents a user request to minimize the app window + */ +public class MinimizeAppRequestEvent extends BaseEvent { + @Override + public String toString() { + return "Request to minimize app window"; + } +} diff --git a/src/main/java/address/events/controller/ResizeAppRequestEvent.java b/src/main/java/address/events/controller/ResizeAppRequestEvent.java new file mode 100644 index 000000000000..bcf5e91d5ce9 --- /dev/null +++ b/src/main/java/address/events/controller/ResizeAppRequestEvent.java @@ -0,0 +1,13 @@ +package address.events.controller; + +import address.events.BaseEvent; + +/** + * Represents a user request to resize the app window (toggles between default size and max size) + */ +public class ResizeAppRequestEvent extends BaseEvent { + @Override + public String toString() { + return "Request to resize app window"; + } +} diff --git a/src/main/java/address/events/hotkey/AcceleratorIgnoredEvent.java b/src/main/java/address/events/hotkey/AcceleratorIgnoredEvent.java new file mode 100644 index 000000000000..2c6cb209b54b --- /dev/null +++ b/src/main/java/address/events/hotkey/AcceleratorIgnoredEvent.java @@ -0,0 +1,25 @@ +package address.events.hotkey; + +import address.events.BaseEvent; + +/** + * Indicates an accelerator key binding was detected and ignored. + */ +public class AcceleratorIgnoredEvent extends BaseEvent { + private String acceleratorName; + + public AcceleratorIgnoredEvent(String acceleratorName) { + this.acceleratorName = acceleratorName; + } + + @Override + public String toString() { + return this.getClass().getSimpleName() + " " + acceleratorName; + } + + @Override + public boolean hasSameIntentionAs(BaseEvent other){ + return (other instanceof AcceleratorIgnoredEvent) + && this.acceleratorName.equals(((AcceleratorIgnoredEvent) other).acceleratorName); + } +} diff --git a/src/main/java/address/events/hotkey/GlobalHotkeyEvent.java b/src/main/java/address/events/hotkey/GlobalHotkeyEvent.java new file mode 100644 index 000000000000..cc8d39be3a97 --- /dev/null +++ b/src/main/java/address/events/hotkey/GlobalHotkeyEvent.java @@ -0,0 +1,25 @@ +package address.events.hotkey; + +import address.events.BaseEvent; +import javafx.scene.input.KeyCombination; + +/** + * Represents a global hotkey event. + * Used as an intermediary event to be raised by jKeyMaster infrastructure. + * Wraps around a {@link KeyBindingEvent} that will be raised by the code handling this event. + */ +public class GlobalHotkeyEvent extends BaseEvent { + + /** The key binding event that represents the global hotkey*/ + public final KeyBindingEvent keyBindingEvent; + + public GlobalHotkeyEvent(KeyCombination keyCombination) { + this.keyBindingEvent = new KeyBindingEvent(keyCombination); + } + + @Override + public String toString() { + final String className = this.getClass().getSimpleName(); + return className + " : keyCombination is " + keyBindingEvent.keyCombination.get().getDisplayText(); + } +} diff --git a/src/main/java/address/events/hotkey/KeyBindingEvent.java b/src/main/java/address/events/hotkey/KeyBindingEvent.java new file mode 100644 index 000000000000..6a36720f71f6 --- /dev/null +++ b/src/main/java/address/events/hotkey/KeyBindingEvent.java @@ -0,0 +1,52 @@ +package address.events.hotkey; + +import address.events.BaseEvent; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; + +import java.util.Optional; + +/** + * Indicates a key event occurred that is potentially a key binding being used + */ +public class KeyBindingEvent extends BaseEvent { + + /** The key event that triggered this event*/ + public Optional keyEvent = Optional.empty(); + + /** A key combination that matches the key event. + * This is used when a key event is not accessible.*/ + public Optional keyCombination = Optional.empty(); + + /** The time that the Key event occurred */ + public long time; + + public KeyBindingEvent(KeyEvent keyEvent){ + this.time = System.nanoTime(); + this.keyEvent = Optional.of(keyEvent); + } + + public KeyBindingEvent(KeyCombination keyCombination) { + this.time = System.nanoTime(); + this.keyCombination = Optional.of(keyCombination); + } + + /** + * @param potentialMatch + * @return true if the key combination matches this key binding + */ + public boolean isMatching(KeyCombination potentialMatch) { + if (keyEvent.isPresent()){ + return potentialMatch.match(keyEvent.get()); + } else { + return potentialMatch.equals(keyCombination.get()); + } + } + + @Override + public String toString() { + final String className = this.getClass().getSimpleName(); + return className + " " + (keyEvent.isPresent() ? keyEvent.get().getText() : + keyCombination.get().getDisplayText()); + } +} diff --git a/src/main/java/address/events/model/LocalModelChangedEvent.java b/src/main/java/address/events/model/LocalModelChangedEvent.java new file mode 100644 index 000000000000..078f3828bc59 --- /dev/null +++ b/src/main/java/address/events/model/LocalModelChangedEvent.java @@ -0,0 +1,19 @@ +package address.events.model; + +import address.events.BaseEvent; +import address.model.datatypes.ReadOnlyAddressBook; + +/** Indicates data in the model has changed*/ +public class LocalModelChangedEvent extends BaseEvent { + + public final ReadOnlyAddressBook data; + + public LocalModelChangedEvent(ReadOnlyAddressBook data){ + this.data = data; + } + + @Override + public String toString() { + return "number of persons " + data.getPersonList().size() + ", number of tags " + data.getTagList().size(); + } +} diff --git a/src/main/java/address/events/parser/FilterCommittedEvent.java b/src/main/java/address/events/parser/FilterCommittedEvent.java new file mode 100644 index 000000000000..1ea54faaf733 --- /dev/null +++ b/src/main/java/address/events/parser/FilterCommittedEvent.java @@ -0,0 +1,21 @@ +package address.events.parser; + +import address.events.BaseEvent; +import address.parser.expr.Expr; + +/** + * Indicates that a filter was committed by the user. + */ +public class FilterCommittedEvent extends BaseEvent { + + public final Expr filterExpression; + + public FilterCommittedEvent(Expr filterExpression) { + this.filterExpression = filterExpression; + } + + @Override + public String toString() { + return filterExpression.toString(); + } +} diff --git a/src/main/java/address/events/storage/FileOpeningExceptionEvent.java b/src/main/java/address/events/storage/FileOpeningExceptionEvent.java new file mode 100644 index 000000000000..d2dca5679c25 --- /dev/null +++ b/src/main/java/address/events/storage/FileOpeningExceptionEvent.java @@ -0,0 +1,24 @@ +package address.events.storage; + +import address.events.BaseEvent; + +import java.io.File; + +/** + * Indicates an exception during a file opening + */ +public class FileOpeningExceptionEvent extends BaseEvent { + + public Exception exception; + public File file; + + public FileOpeningExceptionEvent(Exception exception, File file){ + this.exception = exception; + this.file = file; + } + + @Override + public String toString(){ + return exception + " while opening " + file.getName(); + } +} diff --git a/src/main/java/address/events/storage/FileSavingExceptionEvent.java b/src/main/java/address/events/storage/FileSavingExceptionEvent.java new file mode 100644 index 000000000000..b6bd6a7f0a4f --- /dev/null +++ b/src/main/java/address/events/storage/FileSavingExceptionEvent.java @@ -0,0 +1,24 @@ +package address.events.storage; + +import address.events.BaseEvent; + +import java.io.File; + +/** + * Indicates an exception during a file saving + */ +public class FileSavingExceptionEvent extends BaseEvent { + + public Exception exception; + public File file; + + public FileSavingExceptionEvent(Exception exception, File file) { + this.exception = exception; + this.file = file; + } + + @Override + public String toString(){ + return exception.toString(); + } +} diff --git a/src/main/java/address/events/storage/LoadDataRequestEvent.java b/src/main/java/address/events/storage/LoadDataRequestEvent.java new file mode 100644 index 000000000000..f62e4b14c9f2 --- /dev/null +++ b/src/main/java/address/events/storage/LoadDataRequestEvent.java @@ -0,0 +1,23 @@ +package address.events.storage; + +import address.events.BaseEvent; + +import java.io.File; + +/** + * Indicates a request to load data from the file + */ +public class LoadDataRequestEvent extends BaseEvent { + + /** The file from which the data to be loaded */ + public File file; + + public LoadDataRequestEvent(File file) { + this.file = file; + } + + @Override + public String toString(){ + return "from " + file; + } +} diff --git a/src/main/java/address/events/storage/SaveDataRequestEvent.java b/src/main/java/address/events/storage/SaveDataRequestEvent.java new file mode 100644 index 000000000000..6a8192c6b3d9 --- /dev/null +++ b/src/main/java/address/events/storage/SaveDataRequestEvent.java @@ -0,0 +1,27 @@ +package address.events.storage; + +import address.events.BaseEvent; +import address.model.datatypes.ReadOnlyAddressBook; + +import java.io.File; + +/** + * Indicates a request for saving data has been raised + */ +public class SaveDataRequestEvent extends BaseEvent { + + /** The file to which the data should be saved */ + public final File file; + + public final ReadOnlyAddressBook data; + + public SaveDataRequestEvent(File file, ReadOnlyAddressBook data) { + this.file = file; + this.data = data; + } + + @Override + public String toString(){ + return file.toString(); + } +} diff --git a/src/main/java/address/events/storage/SavePrefsRequestEvent.java b/src/main/java/address/events/storage/SavePrefsRequestEvent.java new file mode 100644 index 000000000000..3b080d674fca --- /dev/null +++ b/src/main/java/address/events/storage/SavePrefsRequestEvent.java @@ -0,0 +1,21 @@ +package address.events.storage; + +import address.events.BaseEvent; +import address.model.UserPrefs; + +/** + * Indicates a request for saving preferences has been raised + */ +public class SavePrefsRequestEvent extends BaseEvent { + + public final UserPrefs prefs; + + public SavePrefsRequestEvent(UserPrefs prefs) { + this.prefs = prefs; + } + + @Override + public String toString(){ + return prefs.toString(); + } +} diff --git a/src/main/java/address/exceptions/DataConversionException.java b/src/main/java/address/exceptions/DataConversionException.java new file mode 100644 index 000000000000..e7350613dc77 --- /dev/null +++ b/src/main/java/address/exceptions/DataConversionException.java @@ -0,0 +1,14 @@ +package address.exceptions; + +/** + * Represents an error during conversion of data from one format to another + */ +public class DataConversionException extends Exception { + public DataConversionException(Exception cause) { + super(cause); + } + + public DataConversionException(String cause) { + super(cause); + } +} diff --git a/src/main/java/address/exceptions/DependencyCheckException.java b/src/main/java/address/exceptions/DependencyCheckException.java new file mode 100644 index 000000000000..f2f329fa8663 --- /dev/null +++ b/src/main/java/address/exceptions/DependencyCheckException.java @@ -0,0 +1,14 @@ +package address.exceptions; + +/** + * Represents a failure in checking application dependency + */ +public class DependencyCheckException extends Exception { + public DependencyCheckException(Exception cause) { + super(cause); + } + + public DependencyCheckException(String cause) { + super(cause); + } +} diff --git a/src/main/java/address/exceptions/DuplicateDataException.java b/src/main/java/address/exceptions/DuplicateDataException.java new file mode 100644 index 000000000000..c82b8d0d62ff --- /dev/null +++ b/src/main/java/address/exceptions/DuplicateDataException.java @@ -0,0 +1,19 @@ +package address.exceptions; + +public class DuplicateDataException extends Exception { + + private final String cause; + + public DuplicateDataException(String cause) { + this.cause = cause; + } + + protected DuplicateDataException() { + cause = "Unspecified cause"; + } + + @Override + public String toString() { + return "This action would result in duplicate data items: " + cause; + } +} diff --git a/src/main/java/address/exceptions/DuplicatePersonException.java b/src/main/java/address/exceptions/DuplicatePersonException.java new file mode 100644 index 000000000000..dd340f53a6ba --- /dev/null +++ b/src/main/java/address/exceptions/DuplicatePersonException.java @@ -0,0 +1,17 @@ +package address.exceptions; + +import address.model.datatypes.person.ReadOnlyPerson; + +public class DuplicatePersonException extends DuplicateDataException { + + private final ReadOnlyPerson offender; + + public DuplicatePersonException(ReadOnlyPerson dup) { + offender = dup; + } + + @Override + public String toString() { + return "Duplicate person not allowed: " + offender + " already exists!"; + } +} diff --git a/src/main/java/address/exceptions/DuplicateTagException.java b/src/main/java/address/exceptions/DuplicateTagException.java new file mode 100644 index 000000000000..e9ce8f0dea92 --- /dev/null +++ b/src/main/java/address/exceptions/DuplicateTagException.java @@ -0,0 +1,17 @@ +package address.exceptions; + +import address.model.datatypes.tag.Tag; + +public class DuplicateTagException extends DuplicateDataException { + + private final Tag offender; + + public DuplicateTagException(Tag tag) { + offender = tag; + } + + @Override + public String toString() { + return "Duplicate tag not allowed: " + offender + " already exists!"; + } +} diff --git a/src/main/java/address/main/ComponentManager.java b/src/main/java/address/main/ComponentManager.java new file mode 100644 index 000000000000..5608ca427b02 --- /dev/null +++ b/src/main/java/address/main/ComponentManager.java @@ -0,0 +1,37 @@ +package address.main; + +import address.events.BaseEvent; +import address.events.EventManager; + +/** + * Base class for *Manager classes + * + * Registers the class' event handlers in eventManager + */ +public abstract class ComponentManager { + protected EventManager eventManager; + + /** + * Uses default {@link EventManager} + */ + public ComponentManager(){ + this(EventManager.getInstance()); + } + + public ComponentManager(EventManager eventManager) { + this.eventManager = eventManager; + eventManager.registerHandler(this); + } + + /** + * Injects the {@link EventManager} dependency + * @param eventManager + */ + public void setEventManager(EventManager eventManager) { + this.eventManager = eventManager; + } + + protected void raise(BaseEvent event){ + eventManager.post(event); + } +} diff --git a/src/main/java/address/model/ModelManager.java b/src/main/java/address/model/ModelManager.java new file mode 100644 index 000000000000..68e9cd5a41dc --- /dev/null +++ b/src/main/java/address/model/ModelManager.java @@ -0,0 +1,194 @@ +package address.model; + +import address.events.model.LocalModelChangedEvent; +import address.exceptions.DuplicateTagException; +import address.main.ComponentManager; +import address.model.datatypes.AddressBook; +import address.model.datatypes.ReadOnlyAddressBook; +import address.model.datatypes.person.Person; +import address.model.datatypes.person.ReadOnlyPerson; +import address.model.datatypes.tag.Tag; +import address.util.AppLogger; +import address.util.Config; +import address.util.LoggerManager; +import address.util.collections.UnmodifiableObservableList; +import javafx.collections.ObservableList; + +import java.util.Collection; +import java.util.List; +import java.util.Optional; +import java.util.UUID; + +/** + * Represents the in-memory model of the address book data. + * All changes to any model should be synchronized. + */ +public class ModelManager extends ComponentManager implements ReadOnlyAddressBook { + private static final AppLogger logger = LoggerManager.getLogger(ModelManager.class); + + private final AddressBook backingModel; + + public static final int GRACE_PERIOD_DURATION = 3; + + /** + * Initializes a ModelManager with the given AddressBook + * AddressBook and its variables should not be null + */ + public ModelManager(AddressBook src, Config config) { + super(); + if (src == null) { + logger.fatal("Attempted to initialize with a null AddressBook"); + assert false; + } + logger.debug("Initializing with address book: {}", src); + + backingModel = new AddressBook(src); + } + + public ModelManager(Config config) { + this(new AddressBook(), config); + } + + public ReadOnlyAddressBook getDefaultAddressBook() { + return new AddressBook(); + } + + /** + * Clears existing backing model and replaces with the provided new data. + */ + public void resetData(ReadOnlyAddressBook newData) { + backingModel.resetData(newData); + } + +//// EXPOSING MODEL + + @Override + public List getPersonList() { + return backingModel.getPersonList(); + } + + @Override + public List getTagList() { + return backingModel.getTagList(); + } + + @Override + public UnmodifiableObservableList getPersonsAsReadOnlyObservableList() { + return backingModel.getPersonsAsReadOnlyObservableList(); + } + + @Override + public UnmodifiableObservableList getTagsAsReadOnlyObservableList() { + return backingModel.getTagsAsReadOnlyObservableList(); + } + + /** + * @return reference to the tags list inside backing model + */ + private ObservableList backingTagList() { + return backingModel.getTags(); + } + +//// UI COMMANDS + + /** + * Request to create a person. Simulates the change optimistically until remote confirmation, and provides a grace + * period for cancellation, editing, or deleting. + */ + public synchronized void createPersonThroughUI(Optional person) { + Person toAdd; + do { // make sure no id clashes. + toAdd = new Person(Math.abs(UUID.randomUUID().hashCode())); + } while (backingModel.getPersonList().contains(toAdd)); + toAdd.update(person.get()); + backingModel.addPerson(toAdd); + updateBackingStorage(); + } + + /** + * Request to update a person. Simulates the change optimistically until remote confirmation, and provides a grace + * period for cancellation, editing, or deleting. TODO listen on Person properties and not manually raise events + * @param target The Person to be changed. + */ + public synchronized void editPersonThroughUI(ReadOnlyPerson target, + Optional editedTarget) { + backingModel.findPerson(target).get().update(editedTarget.get()); + updateBackingStorage(); + } + + public void updateBackingStorage() { + raise(new LocalModelChangedEvent(backingModel)); + } + + /** + * Request to set the tags for a group of Persons. Simulates change optimistically until remote confirmation, + * and provides a grace period for cancellation, editing, or deleting. + * @param targets Persons to be retagged + */ + public void retagPersonsThroughUI(Collection targets, + Optional> newTags) { + + targets.stream().forEach(p -> backingModel.findPerson(p).get().setTags(newTags.get())); + updateBackingStorage(); + } + + /** + * Request to delete a person. Simulates the change optimistically until remote confirmation, and provides a grace + * period for cancellation, editing, or deleting. + */ + public synchronized void deletePersonThroughUI(ReadOnlyPerson target) { + try { + backingModel.removePerson(target); + updateBackingStorage(); + } catch (Exception e) { + e.printStackTrace(); + } + } + + public synchronized void deletePersonsThroughUI(List targets) { + targets.forEach(backingModel::removePerson); + updateBackingStorage(); + } + +//// CREATE + + /** + * Adds a tag to the model + * @param tagToAdd + * @throws DuplicateTagException when this operation would cause duplicates + */ + public synchronized void addTagToBackingModel(Tag tagToAdd) throws DuplicateTagException { + if (backingTagList().contains(tagToAdd)) { + throw new DuplicateTagException(tagToAdd); + } + backingTagList().add(tagToAdd); + } + +//// UPDATE + + /** + * Updates the details of a Tag object. Updates to Tag objects should be + * done through this method to ensure the proper events are raised to indicate + * a change to the model. TODO listen on Tag properties and not manually raise events here. + * + * @param original The Tag object to be changed. + * @param updated The temporary Tag object containing new values. + */ + public synchronized void updateTag(Tag original, Tag updated) throws DuplicateTagException { + if (!original.equals(updated) && backingTagList().contains(updated)) { + throw new DuplicateTagException(updated); + } + original.update(updated); + } + +//// DELETE + + /** + * Deletes the tag from the model. + * @param tagToDelete + * @return true if there was a successful removal + */ + public synchronized boolean deleteTag(Tag tagToDelete) { + return backingTagList().remove(tagToDelete); + } +} diff --git a/src/main/java/address/model/UserPrefs.java b/src/main/java/address/model/UserPrefs.java new file mode 100644 index 000000000000..b3b8f112bd6b --- /dev/null +++ b/src/main/java/address/model/UserPrefs.java @@ -0,0 +1,19 @@ +package address.model; + +import address.util.GuiSettings; + +/** + * Represents User's preferences. + */ +public class UserPrefs { + + public GuiSettings guiSettings; + + public GuiSettings getGuiSettings() { + return guiSettings == null ? new GuiSettings() : guiSettings; + } + + public void setGuiSettings(GuiSettings guiSettings) { + this.guiSettings = guiSettings; + } +} diff --git a/src/main/java/address/model/datatypes/AddressBook.java b/src/main/java/address/model/datatypes/AddressBook.java new file mode 100644 index 000000000000..fb8ca1643e5d --- /dev/null +++ b/src/main/java/address/model/datatypes/AddressBook.java @@ -0,0 +1,151 @@ +package address.model.datatypes; + +import address.model.datatypes.person.Person; +import address.model.datatypes.person.ReadOnlyPerson; +import address.model.datatypes.tag.Tag; +import address.util.collections.UnmodifiableObservableList; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Wraps all data at the address-book level + * Duplicates are not allowed (by .equals comparison) + */ +public class AddressBook implements ReadOnlyAddressBook { + + private final ObservableList persons; + private final ObservableList tags; + + { + persons = FXCollections.observableArrayList(); + tags = FXCollections.observableArrayList(); + } + + public AddressBook() {} + + /** + * Persons and Tags are copied into this addressbook + */ + public AddressBook(ReadOnlyAddressBook toBeCopied) { + this(toBeCopied.getPersonList(), toBeCopied.getTagList()); + } + + /** + * Persons and Tags are copied into this addressbook + */ + public AddressBook(List persons, List tags) { + resetData(persons, tags); + } + +//// list overwrite operations + + public ObservableList getPersons() { + return persons; + } + + public ObservableList getTags() { + return tags; + } + + public void setPersons(List persons) { + this.persons.setAll(persons); + } + + public void setTags(Collection tags) { + this.tags.setAll(tags); + } + + public void clearData() { + persons.clear(); + tags.clear(); + } + + public void resetData(Collection newPersons, Collection newTags) { + setPersons(newPersons.stream().map(Person::new).collect(Collectors.toList())); + setTags(newTags); + } + + public void resetData(ReadOnlyAddressBook newData) { + resetData(newData.getPersonList(), newData.getTagList()); + } + +//// person-level operations + + public boolean containsPerson(ReadOnlyPerson key) { + return ReadOnlyPerson.containsById(persons, key); + } + + public boolean containsPerson(int id) { + return ReadOnlyPerson.containsById(persons, id); + } + + public Optional findPerson(ReadOnlyPerson key) { + return ReadOnlyPerson.findById(persons, key); + } + + public Optional findPerson(int id) { + return ReadOnlyPerson.findById(persons, id); + } + + public void addPerson(Person p){ + persons.add(p); + } + + public boolean removePerson(ReadOnlyPerson key) { + return ReadOnlyPerson.removeOneById(persons, key); + } + + public boolean removePerson(int id) { + return ReadOnlyPerson.removeOneById(persons, id); + } + +//// tag-level operations + + public void addTag(Tag t){ + tags.add(t); + } + + public boolean removeTag(Tag t) { + return tags.remove(t); + } + +//// util methods + + // Deprecated (to be removed when no-dupe property is properly enforced + public boolean containsDuplicates() { + return !UniqueData.itemsAreUnique(persons) || !UniqueData.itemsAreUnique(tags); + } + + @Override + public String toString() { + return persons.size() + " persons, " + tags.size() + " tags"; + // TODO: refine later + } + + @Override + public List getPersonList() { + return Collections.unmodifiableList(persons); + } + + @Override + public List getTagList() { + return Collections.unmodifiableList(tags); + } + + @Override + public UnmodifiableObservableList getPersonsAsReadOnlyObservableList() { + return new UnmodifiableObservableList<>(persons); + } + + @Override + public UnmodifiableObservableList getTagsAsReadOnlyObservableList() { + return new UnmodifiableObservableList<>(tags); + } + +} diff --git a/src/main/java/address/model/datatypes/ExtractableObservables.java b/src/main/java/address/model/datatypes/ExtractableObservables.java new file mode 100644 index 000000000000..8050575c3c33 --- /dev/null +++ b/src/main/java/address/model/datatypes/ExtractableObservables.java @@ -0,0 +1,35 @@ +package address.model.datatypes; + +import javafx.beans.Observable; +import javafx.util.Callback; + +import java.util.List; + +/** + * Utility methods for extracting all publicly relevant {@code Observable} fields for easy full object binding and + * invalidation listening. + * + * @see javafx.collections.FXCollections#observableArrayList(Callback) + * @see javafx.collections.FXCollections#observableList(List, Callback) + */ +public interface ExtractableObservables { + + /** + * Useful as extractor argument to {@link javafx.collections.FXCollections#observableArrayList(Callback)} or + * {@link javafx.collections.FXCollections#observableList(List, Callback)}. + * Same as calling {@code source.extractObservables()}. + * + * @see #extractObservables() + * @param source + * @return all publicly relevant {@code Observable} fields in {@code source}. + */ + static Observable[] extractFrom(ExtractableObservables source) { + return source.extractObservables(); + } + + /** + * @see #extractFrom(ExtractableObservables) + * @return all publicly relevant {@code Observable} fields in this object. + */ + Observable[] extractObservables(); +} diff --git a/src/main/java/address/model/datatypes/ReadOnlyAddressBook.java b/src/main/java/address/model/datatypes/ReadOnlyAddressBook.java new file mode 100644 index 000000000000..eb747a7aad96 --- /dev/null +++ b/src/main/java/address/model/datatypes/ReadOnlyAddressBook.java @@ -0,0 +1,40 @@ +package address.model.datatypes; + +import address.model.datatypes.person.ReadOnlyPerson; +import address.model.datatypes.tag.Tag; +import address.util.collections.UnmodifiableObservableList; + +import java.util.List; + +/** + * Unmodifiable view of an address book + */ +public interface ReadOnlyAddressBook { + + /** + * @return unmodifiable view of persons list + */ + List getPersonList(); + + /** + * @return unmodifiable view of tags list + */ + List getTagList(); + +//// below method implementations are optional; override if functionality is needed. + + /** + * @return all persons in backing model IN AN UNMODIFIABLE VIEW + */ + default UnmodifiableObservableList getPersonsAsReadOnlyObservableList() { + throw new UnsupportedOperationException(); + } + + /** + * @return all tags in backing model IN AN UNMODIFIABLE VIEW + */ + default UnmodifiableObservableList getTagsAsReadOnlyObservableList() { + throw new UnsupportedOperationException(); + } + +} diff --git a/src/main/java/address/model/datatypes/UniqueData.java b/src/main/java/address/model/datatypes/UniqueData.java new file mode 100644 index 000000000000..3c3ba5add5e1 --- /dev/null +++ b/src/main/java/address/model/datatypes/UniqueData.java @@ -0,0 +1,65 @@ +package address.model.datatypes; + +import java.util.Collection; +import java.util.HashSet; +import java.util.Set; + +/** + * Indicates datatypes that require some form of uniqueness enforcement. + * Includes utility methods for checking uniqueness of collections of data. + * + * This is an abstract class to force implementations of the core methods below. + */ +public abstract class UniqueData { + + /** + * Checks that the argument collection fulfills the set property. + * + * @param items collection of items to be tested + * @return true if no duplicates found in items + */ + public static boolean itemsAreUnique(Collection items) { + final Set test = new HashSet<>(); + for (D item : items) { + if (!test.add(item)) return false; + } + return true; + } + + @SafeVarargs + public static boolean canCombineWithoutDuplicates(Collection first, + Collection... rest) { + return areUniqueAndDisjoint(first, rest); + } + + /** + * Checks that the argument collections fulfill the Set property and are disjoint. + * + * @return true if every collection in the arguments contains no duplicates and are disjoint + */ + @SafeVarargs + public static boolean areUniqueAndDisjoint(Collection head, Collection... tail) { + final Set test = new HashSet<>(); + for (D item : head) { + if (!test.add(item)) return false; + } + for (Collection items : tail) { + for (D item : items) { + if (!test.add(item)) return false; + } + } + return true; + } + + // force implementation of custom equals and hashcode + @Override + public abstract boolean equals(Object other); + @Override + public abstract int hashCode(); + + /** + * Override with meaningful string representation of object + */ + @Override + public abstract String toString(); +} diff --git a/src/main/java/address/model/datatypes/person/Person.java b/src/main/java/address/model/datatypes/person/Person.java new file mode 100644 index 000000000000..7b804c8454c7 --- /dev/null +++ b/src/main/java/address/model/datatypes/person/Person.java @@ -0,0 +1,337 @@ +package address.model.datatypes.person; + +import address.model.datatypes.UniqueData; +import address.model.datatypes.tag.Tag; +import address.util.collections.UnmodifiableObservableList; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonSetter; +import javafx.beans.property.*; +import javafx.collections.FXCollections; +import javafx.collections.ObservableList; + +import java.time.LocalDate; +import java.util.Collection; +import java.util.Collections; +import java.util.List; +import java.util.function.BiConsumer; + +/** + * Data-model implementation class representing the "Person" domain object. + * id is immutable, so this class can be safely used as map keys and set elements. + * + * As far as possible, avoid working directly with this class. + * Instead, use and declare the minimum required superclass/interface. + * + * Eg. A GUI element controller that only needs access to the Person's properties should declare the received Person + * as an ReadOnlyPerson -- since it does not need the functionality in the other superclasses/interfaces. + */ +public class Person extends UniqueData implements ReadOnlyPerson { + + private final int id; + + private final StringProperty firstName; + private final StringProperty lastName; + + private final StringProperty githubUsername; + private final StringProperty street; + private final StringProperty postalCode; + private final StringProperty city; + + private final SimpleObjectProperty birthday; + private final ObservableList tags; + private final SimpleBooleanProperty isDeleted; + + // defaults + { + firstName = new SimpleStringProperty(""); + lastName = new SimpleStringProperty(""); + + street = new SimpleStringProperty(""); + postalCode = new SimpleStringProperty(""); + city = new SimpleStringProperty(""); + githubUsername = new SimpleStringProperty(""); + + birthday = new SimpleObjectProperty<>(); + + tags = FXCollections.observableArrayList(); + isDeleted = new SimpleBooleanProperty(false); + } + + /** + * id-less person data container + */ + public static Person createPersonDataContainer() { + return new Person(0); + } + + public Person(int id) { + this.id = id; + } + + /** + * Constructor with firstName and lastName parameters. + * Other parameters are set to defaults. + */ + public Person(String firstName, String lastName, int id) { + this(id); + setFirstName(firstName); + setLastName(lastName); + } + + /** + * Deep copy constructor. Also copies id. + * @see Person#update(ReadOnlyPerson) + */ + public Person(ReadOnlyPerson toBeCopied) { + this(toBeCopied.getId()); + update(toBeCopied); + } + + /** + * Does not update own id with argument's id. + * @return self (calling this from a Person returns a Person instead of just a WritablePerson) + */ + public Person update(ReadOnlyPerson newDataSource) { + setFirstName(newDataSource.getFirstName()); + setLastName(newDataSource.getLastName()); + + setStreet(newDataSource.getStreet()); + setPostalCode(newDataSource.getPostalCode()); + setCity(newDataSource.getCity()); + setGithubUsername(newDataSource.getGithubUsername()); + + setBirthday(newDataSource.getBirthday()); + setTags(newDataSource.getTagList()); + setIsDeleted(false); // TODO: change when isDeleted is fully implemented + return this; + } + + // TODO: consider using reflection to access all isassignablefrom(Property) returning methods for maintainability + /** + * Passes matching property field pairs (paired between self and another ReadOnlyPerson) as arguments to a callback. + * The callback is called once for each property field in the ObservabePerson class. + * + * @param other the ReadOnlyPerson whose property fields make up the second parts of the property pairs + * @param action called for every property field: action(self:property, other:same_property) + * first argument is from self, second is from the "other" parameter + */ + public void forEachPropertyFieldPairWith(Person other, BiConsumer action) { + action.accept(firstName, other.firstName); + action.accept(lastName, other.lastName); + action.accept(githubUsername, other.githubUsername); + + action.accept(street, other.street); + action.accept(postalCode, other.postalCode); + action.accept(city, other.city); + + action.accept(birthday, other.birthday); + } + +//// id + + @JsonProperty("id") + @Override + public int getId() { + return id; + } + +//// NAME + + @JsonProperty("firstName") + @Override + public String getFirstName() { + return firstName.get(); + } + + public void setFirstName(String firstName) { + this.firstName.set(firstName); + } + + @Override + public ReadOnlyStringProperty firstNameProperty() { + return firstName; + } + + @JsonProperty("lastName") + @Override + public String getLastName() { + return lastName.get(); + } + + public void setLastName(String lastName) { + this.lastName.set(lastName); + } + + @Override + public ReadOnlyStringProperty lastNameProperty() { + return lastName; + } + +//// GITHUB USERNAME + + @JsonProperty("githubUsername") + public String getGithubUsername() { + return githubUsername.get(); + } + + public void setGithubUsername(String githubUsername) { + this.githubUsername.set(githubUsername); + } + + public ReadOnlyStringProperty githubUsernameProperty() { + return githubUsername; + } + +//// STREET + + @JsonProperty("street") + @Override + public String getStreet() { + return street.get(); + } + + public void setStreet(String street) { + this.street.set(street); + } + + @Override + public ReadOnlyStringProperty streetProperty() { + return street; + } + +//// POSTAL CODE + + @JsonProperty("postalCode") + @Override + public String getPostalCode() { + return postalCode.get(); + } + + public void setPostalCode(String postalCode) { + this.postalCode.set(postalCode); + } + + @Override + public ReadOnlyStringProperty postalCodeProperty() { + return postalCode; + } + +//// CITY + + @JsonProperty("city") + @Override + public String getCity() { + return city.get(); + } + + public void setCity(String city) { + this.city.set(city); + } + + @Override + public ReadOnlyStringProperty cityProperty() { + return city; + } + +//// BIRTHDAY + + @JsonProperty("birthday") + @Override + public LocalDate getBirthday() { + return birthday.get(); + } + + @JsonSetter("birthday") + public void setBirthday(LocalDate birthday) { + this.birthday.set(birthday); + } + + @Override + public ReadOnlyObjectProperty birthdayProperty() { + return birthday; + } + +//// TAGS + + @Override + public List getTagList() { + return Collections.unmodifiableList(tags); + } + + @Override + public UnmodifiableObservableList getObservableTagList() { + return new UnmodifiableObservableList<>(tags); + } + + @Override + public boolean hasName(String firstName, String lastName) { + return this.firstName.get().equals(firstName) && this.lastName.get().equals(lastName); + } + + @JsonProperty("tags") + public List getTags() { + return tags; + } + + public void setTags(Collection tags) { + this.tags.clear(); + this.tags.addAll(tags); + } + + @JsonProperty("isDeleted") + public boolean isDeleted() { + return isDeleted.get(); + } + + public SimpleBooleanProperty isDeletedProperty() { + return isDeleted; + } + + public void setIsDeleted(boolean isDeleted) { + this.isDeleted.set(isDeleted); + } + + //// OTHER LOGIC + + /** + * Compares id + */ + @Override + public boolean equals(Object other) { + if (other == this) return true; + if (other == null) return false; + if (Person.class.isAssignableFrom(other.getClass())) { + final ReadOnlyPerson otherP = (ReadOnlyPerson) other; + return getId() == otherP.getId(); + } + return false; + } + + @Override + public int hashCode() { + return id; + } + + @Override + public String toString() { + return "Person [" + idString() + "][" + fullName() + + "][Street:" + getStreet() + + "][City:" + getCity() + + "][Postal code:" + getPostalCode() + + "][Birthday:" + birthdayString() + + "][GitHub Username:" + getGithubUsername() + + "][" + tagsString() + "]"; + } + + public Person copy() { + Person copy = new Person(this.getFirstName(), this.getLastName(), this.getId()); + copy.setBirthday(this.getBirthday()); + copy.setCity(this.getCity()); + copy.setPostalCode(this.getPostalCode()); + copy.setStreet(this.getStreet()); + copy.setGithubUsername(this.getGithubUsername()); + copy.setTags(this.getTags()); + return copy; + } + +} diff --git a/src/main/java/address/model/datatypes/person/ReadOnlyPerson.java b/src/main/java/address/model/datatypes/person/ReadOnlyPerson.java new file mode 100644 index 000000000000..1fd28bb8e779 --- /dev/null +++ b/src/main/java/address/model/datatypes/person/ReadOnlyPerson.java @@ -0,0 +1,244 @@ +package address.model.datatypes.person; + +import address.model.datatypes.ExtractableObservables; +import address.model.datatypes.tag.Tag; +import address.util.collections.UnmodifiableObservableList; +import commons.DateTimeUtil; +import javafx.beans.Observable; +import javafx.beans.property.ReadOnlyObjectProperty; +import javafx.beans.property.ReadOnlyStringProperty; + +import java.net.MalformedURLException; +import java.net.URL; +import java.time.LocalDate; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Allows read-only access to the Person domain object's data. + */ +public interface ReadOnlyPerson extends ExtractableObservables { + + /** + * @param key ReadOnlyPerson with ID of the element you wish to remove from {@code col} + * @return whether {@code col} was changed as a result of this operation + */ + static boolean removeOneById(Collection col, ReadOnlyPerson key) { + return removeOneById(col, key.getId()); + } + + /** + * @return whether {@code col} was changed as a result of this operation + */ + static

boolean removeOneById(Collection

col, int id) { + final Optional

toRemove = findById(col, id); + if (toRemove.isPresent()) { + return col.remove(toRemove.get()); + } else { + return false; + } + } + + /** + * @see #removeAllById(Collection, Collection) + * @param col collection to remove from + * @param keys collection of ReadOnlyPersons with the IDs of the those you wish to remove from {@code col} + * @return whether {@code col} was changed as a result of this operation + */ + static boolean removeAllWithSameIds(Collection col, + Collection keys) { + return removeAllById(col, keys.stream().map(e -> e.getId()).collect(Collectors.toList())); + } + + /** + * @see Collection#removeAll(Collection) + * @param ids collection of IDs of the persons you wish to remove from {@code col} + * @return whether {@code col} was changed as a result of this operation + */ + static boolean removeAllById(Collection col, + Collection ids) { + final Set idSet = new HashSet<>(ids); + return col.removeAll(col.stream().filter(p -> idSet.contains(p.getId())).collect(Collectors.toList())); + } + + /** + * @return the first element found in {@code col} with same id as {@code key} + */ + static

Optional

findById(Collection

col, ReadOnlyPerson key) { + return findById(col, key.getId()); + } + + /** + * @return the first element found in {@code col} with same id as {@code id} + */ + static

Optional

findById(Collection

col, int id) { + return col.stream().filter(p -> id == p.getId()).findFirst(); + } + + /** + * @see #containsById(Collection, int) + */ + static boolean containsById(Collection col, ReadOnlyPerson key) { + return containsById(col, key.getId()); + } + + /** + * @see Collection#contains(Object) + */ + static boolean containsById(Collection col, int id) { + return col.stream().anyMatch(e -> id == e.getId()); + } + + /** + * Remote-assigned (canonical) ids are positive integers. + * Locally-assigned temporary ids are negative integers. + * 0 is reserved for ID-less Person data containers. + */ + int getId(); + default String idString() { + return hasConfirmedRemoteID() ? "#" + getId() : "#TBD"; + } + /** + * @see #getId() + */ + default boolean hasConfirmedRemoteID() { + return getId() > 0; + } + + String getFirstName(); + String getLastName(); + /** + * @return first-last format full name + */ + default String fullName() { + return getFirstName() + ' ' + getLastName(); + } + + String getGithubUsername(); + + default URL profilePageUrl(){ + try { + return new URL("https://github.com/" + getGithubUsername()); + } catch (MalformedURLException e) { + try { + return new URL("https://github.com"); + } catch (MalformedURLException e1) { + assert false; + } + } + return null; + } + default Optional githubProfilePicUrl() { + if (getGithubUsername().length() > 0) { + String profilePicUrl = profilePageUrl().toExternalForm() + ".png"; + return Optional.of(profilePicUrl); + } + return Optional.empty(); + } + + String getStreet(); + String getPostalCode(); + String getCity(); + + LocalDate getBirthday(); + /** + * @return birthday date-formatted as string + */ + default String birthdayString() { + if (getBirthday() == null) return ""; + return DateTimeUtil.format(getBirthday()); + } + + /** + * @return unmodifiable list view of tags. + */ + List getTagList(); + /** + * @return string representation of this Person's tags + */ + default String tagsString() { + final StringBuffer buffer = new StringBuffer(); + final String separator = ", "; + getTagList().forEach(tag -> buffer.append(tag).append(separator)); + if (buffer.length() == 0) { + return ""; + } else { + return buffer.substring(0, buffer.length() - separator.length()); + } + } + + default boolean dataFieldsEqual(ReadOnlyPerson other) { + final Set othersTags = new HashSet<>(other.getTagList()); + return fullName().equals(other.fullName()) + && getGithubUsername().equals(other.getGithubUsername()) + && getStreet().equals(other.getStreet()) + && getPostalCode().equals(other.getPostalCode()) + && getCity().equals(other.getCity()) + && getBirthday().equals(other.getBirthday()) + && getTagList().stream().allMatch(othersTags::contains); + } + +//// Operations below are optional; override if they will be needed. +//// Eg. implementing a simple person data object should not require using javafx classes like Property, so the +//// below methods make no sense for that context. + + default ReadOnlyStringProperty firstNameProperty() { + throw new UnsupportedOperationException(); + } + default ReadOnlyStringProperty lastNameProperty() { + throw new UnsupportedOperationException(); + } + default ReadOnlyStringProperty githubUsernameProperty() { + throw new UnsupportedOperationException(); + } + + default ReadOnlyStringProperty streetProperty() { + throw new UnsupportedOperationException(); + } + default ReadOnlyStringProperty postalCodeProperty() { + throw new UnsupportedOperationException(); + } + default ReadOnlyStringProperty cityProperty() { + throw new UnsupportedOperationException(); + } + + default ReadOnlyObjectProperty birthdayProperty() { + throw new UnsupportedOperationException(); + } + + /** + * @return ObservableList unmodifiable view of this Person's tags + */ + default UnmodifiableObservableList getObservableTagList() { + throw new UnsupportedOperationException(); + } + + @Override + default Observable[] extractObservables() { + return new Observable[] { + firstNameProperty(), + lastNameProperty(), + githubUsernameProperty(), + + streetProperty(), + postalCodeProperty(), + cityProperty(), + + birthdayProperty(), + getObservableTagList() + }; + } + + static List getCommonTags(Collection persons) { + Set tags = new HashSet<>(); + persons.stream().forEach(p -> tags.addAll(p.getTagList())); + List assignedTags = tags.stream().filter(tag -> + persons.stream() + .filter(p -> p.getObservableTagList().contains(tag)) + .count() == persons.size()) + .collect(Collectors.toCollection(ArrayList::new)); + return assignedTags; + } + + boolean hasName(String firstName, String lastName); +} diff --git a/src/main/java/address/model/datatypes/tag/SelectableTag.java b/src/main/java/address/model/datatypes/tag/SelectableTag.java new file mode 100644 index 000000000000..3e5e228a65a0 --- /dev/null +++ b/src/main/java/address/model/datatypes/tag/SelectableTag.java @@ -0,0 +1,34 @@ +package address.model.datatypes.tag; + +import com.google.common.base.Objects; + +// TODO CHANGE THIS TO WRAPPER CLASS +public class SelectableTag extends Tag { + private boolean isSelected = false; + + public SelectableTag(Tag tag) { + super(tag.getName()); + } + + public void setSelected(boolean isSelected) { + this.isSelected = isSelected; + } + + public boolean isSelected() { + return this.isSelected; + } + + @Override + public boolean equals(Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + if (!super.equals(o)) return false; + SelectableTag that = (SelectableTag) o; + return isSelected == that.isSelected; + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), isSelected); + } +} diff --git a/src/main/java/address/model/datatypes/tag/Tag.java b/src/main/java/address/model/datatypes/tag/Tag.java new file mode 100644 index 000000000000..f2a00380a59e --- /dev/null +++ b/src/main/java/address/model/datatypes/tag/Tag.java @@ -0,0 +1,73 @@ +package address.model.datatypes.tag; + +import address.model.datatypes.ExtractableObservables; +import address.model.datatypes.UniqueData; +import com.fasterxml.jackson.annotation.JsonIgnore; +import com.fasterxml.jackson.annotation.JsonProperty; +import javafx.beans.Observable; +import javafx.beans.property.SimpleStringProperty; + +public class Tag extends UniqueData implements ExtractableObservables { + + @JsonIgnore private final SimpleStringProperty name; + + { + name = new SimpleStringProperty(""); + } + + public Tag() {} + + public Tag(String name) { + setName(name); + } + + // Copy constructor + public Tag(Tag tag) { + update(tag); + } + + public Tag update(Tag group) { + setName(group.getName()); + return this; + } + + @Override + public Observable[] extractObservables() { + return new Observable[] { name }; + } + + @JsonProperty("name") + public String getName() { + return name.get(); + } + + public void setName(String name) { + this.name.set(name); + } + + + public SimpleStringProperty nameProperty() { + return name; + } + + @Override + public boolean equals(Object otherGroup) { + if (otherGroup == this) return true; + if (otherGroup == null) return false; + if (!Tag.class.isAssignableFrom(otherGroup.getClass())) return false; + + final Tag other = (Tag) otherGroup; + return this.getName().equals(other.getName()); + } + + @Override + public int hashCode() { + return getName().hashCode(); + } + + @Override + public String toString() { + return getName(); + } + +} diff --git a/src/main/java/address/parser/AddCommand.java b/src/main/java/address/parser/AddCommand.java new file mode 100644 index 000000000000..4b97c0bebe9b --- /dev/null +++ b/src/main/java/address/parser/AddCommand.java @@ -0,0 +1,18 @@ +package address.parser; + +import address.model.ModelManager; +import address.model.datatypes.person.Person; +import address.model.datatypes.person.ReadOnlyPerson; + +import java.util.Optional; + +/** + * + */ +public class AddCommand implements Command{ + + @Override + public void execute(ModelManager modelManager) { + modelManager.createPersonThroughUI(Optional.of(new Person("John", "Smith", -1))); + } +} diff --git a/src/main/java/address/parser/Command.java b/src/main/java/address/parser/Command.java new file mode 100644 index 000000000000..bb984102c389 --- /dev/null +++ b/src/main/java/address/parser/Command.java @@ -0,0 +1,12 @@ +package address.parser; + +import address.model.ModelManager; + +/** + * + */ +public interface Command { + + + public void execute(ModelManager modelManager); +} diff --git a/src/main/java/address/parser/CommandParser.java b/src/main/java/address/parser/CommandParser.java new file mode 100644 index 000000000000..241308316fb4 --- /dev/null +++ b/src/main/java/address/parser/CommandParser.java @@ -0,0 +1,32 @@ +package address.parser; + +/** + * + */ +public class CommandParser { + + + + public static boolean isCommandInput(String input) { + String firstWord = input.split(" ")[0].toLowerCase(); + return firstWord.equals("add") || firstWord.equals("delete") || firstWord.equals("edit"); + } + + public static Command parse(String input) { + + if (!isCommandInput(input)) { + throw new IllegalArgumentException("Invalid command input"); + } + + String firstWord = input.split(" ")[0].toLowerCase(); + + switch (firstWord) { + case "add" : + return new AddCommand(); + case "delete" : + return new DeleteCommand(); + default: + return null; + } + } +} diff --git a/src/main/java/address/parser/DeleteCommand.java b/src/main/java/address/parser/DeleteCommand.java new file mode 100644 index 000000000000..150672ed6093 --- /dev/null +++ b/src/main/java/address/parser/DeleteCommand.java @@ -0,0 +1,17 @@ +package address.parser; + +import address.model.ModelManager; + +/** + * + */ +public class DeleteCommand implements Command { + + @Override + public void execute(ModelManager modelManager) { + if (modelManager.getPersonList().size() == 0) { + return; + } + modelManager.deletePersonThroughUI(modelManager.getPersonList().get(0)); + } +} diff --git a/src/main/java/address/parser/ParseException.java b/src/main/java/address/parser/ParseException.java new file mode 100644 index 000000000000..8998361b5d6a --- /dev/null +++ b/src/main/java/address/parser/ParseException.java @@ -0,0 +1,7 @@ +package address.parser; + +public class ParseException extends Exception { + ParseException(String message) { + super(message); + } +} diff --git a/src/main/java/address/parser/Parser.java b/src/main/java/address/parser/Parser.java new file mode 100644 index 000000000000..d34f51ef1017 --- /dev/null +++ b/src/main/java/address/parser/Parser.java @@ -0,0 +1,91 @@ +package address.parser; + +import address.parser.expr.AndExpr; +import address.parser.expr.Expr; +import address.parser.expr.NotExpr; +import address.parser.expr.PredExpr; +import address.parser.qualifier.*; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +public class Parser { + private static final String NEGATION_REGEX = "!(!*\\w+)"; + private static final String EXPR_REGEX = "\\s*(!*\\w+)\\s*:\\s*(\\w+)\\s*"; + + /** + * Parses the given input and returns a representative predicate + * + * @param input + * @return + * @throws ParseException if input has incorrect syntax and/or qualifiers + */ + public Expr parse(String input) throws ParseException { + Expr result = PredExpr.TRUE; + if (input.isEmpty()) return result; + + Pattern pattern = Pattern.compile(EXPR_REGEX, Pattern.CASE_INSENSITIVE); + Matcher matcher = pattern.matcher(input); + + while (!matcher.hitEnd()) { + if (!matcher.find()) throw new ParseException("Part of filter unrecognised"); + Expr intermediate = getPredicate(matcher.group(1), matcher.group(2)); + result = new AndExpr(intermediate, result); + } + + return result; + } + + /** + * Gets the predicate which represents the qualifier name and content + * + * @param qualifierName may be prefixed with multiple ! characters to signify negations + * @param qualifierContent + * @return + * @throws ParseException if qualifier name without prefixed ! is not a valid qualifier string + */ + private Expr getPredicate(String qualifierName, String qualifierContent) throws ParseException { + Matcher matcher = Pattern.compile(NEGATION_REGEX, Pattern.CASE_INSENSITIVE).matcher(qualifierName); + if (matcher.matches()) { + return new NotExpr(getPredicate(matcher.group(1), qualifierContent)); + } + return new PredExpr(getQualifier(qualifierName, qualifierContent)); + } + + /** + * Gets the Qualifier which represents the qualifier name and content + * + * @param qualifierName should match one of the qualifier strings + * @param content + * @return + * @throws ParseException if qualifier name does not match any of the qualifier strings + */ + private Qualifier getQualifier(String qualifierName, String content) throws ParseException { + switch (qualifierName) { + case "city": + return new CityQualifier(content); + case "lastName": + return new LastNameQualifier(content); + case "firstName": + return new FirstNameQualifier(content); + case "name": + return new NameQualifier(content); + case "street": + return new StreetQualifier(content); + case "tag": + return new TagQualifier(content); + case "id": + return new IdQualifier(parseInt(content)); + default: + throw new ParseException("Unrecognised qualifier " + qualifierName); + } + } + + private Integer parseInt(String content) throws ParseException { + try { + return Integer.valueOf(content); + } catch (NumberFormatException e) { + throw new ParseException("Invalid integer: " + content); + } + } +} diff --git a/src/main/java/address/parser/expr/AndExpr.java b/src/main/java/address/parser/expr/AndExpr.java new file mode 100644 index 000000000000..f244fcf4093d --- /dev/null +++ b/src/main/java/address/parser/expr/AndExpr.java @@ -0,0 +1,24 @@ +package address.parser.expr; + +import address.model.datatypes.person.ReadOnlyPerson; + +public class AndExpr implements Expr { + + private final Expr left; + private final Expr right; + + public AndExpr(Expr left, Expr right) { + this.left = left; + this.right = right; + } + + @Override + public boolean satisfies(ReadOnlyPerson person) { + return left.satisfies(person) && right.satisfies(person); + } + + @Override + public String toString() { + return "(" + left + ") AND (" + right + ")"; + } +} diff --git a/src/main/java/address/parser/expr/Expr.java b/src/main/java/address/parser/expr/Expr.java new file mode 100644 index 000000000000..df06c3a5f60c --- /dev/null +++ b/src/main/java/address/parser/expr/Expr.java @@ -0,0 +1,8 @@ +package address.parser.expr; + +import address.model.datatypes.person.ReadOnlyPerson; + +public interface Expr { + boolean satisfies(ReadOnlyPerson person); + String toString(); +} diff --git a/src/main/java/address/parser/expr/NotExpr.java b/src/main/java/address/parser/expr/NotExpr.java new file mode 100644 index 000000000000..dcdd0dc9566c --- /dev/null +++ b/src/main/java/address/parser/expr/NotExpr.java @@ -0,0 +1,21 @@ +package address.parser.expr; + +import address.model.datatypes.person.ReadOnlyPerson; + +public class NotExpr implements Expr { + Expr expr; + + public NotExpr(Expr expr) { + this.expr = expr; + } + + @Override + public boolean satisfies(ReadOnlyPerson person) { + return !expr.satisfies(person); + } + + @Override + public String toString() { + return "NOT(" + expr + ")"; + } +} diff --git a/src/main/java/address/parser/expr/PredExpr.java b/src/main/java/address/parser/expr/PredExpr.java new file mode 100644 index 000000000000..2716cedc6971 --- /dev/null +++ b/src/main/java/address/parser/expr/PredExpr.java @@ -0,0 +1,25 @@ +package address.parser.expr; + +import address.model.datatypes.person.ReadOnlyPerson; +import address.parser.qualifier.Qualifier; +import address.parser.qualifier.TrueQualifier; + +public class PredExpr implements Expr { + public static final PredExpr TRUE = new PredExpr(new TrueQualifier()); + + private final Qualifier qualifier; + + public PredExpr(Qualifier qualifier) { + this.qualifier = qualifier; + } + + @Override + public boolean satisfies(ReadOnlyPerson person) { + return qualifier.run(person); + } + + @Override + public String toString() { + return qualifier.toString(); + } +} diff --git a/src/main/java/address/parser/qualifier/CityQualifier.java b/src/main/java/address/parser/qualifier/CityQualifier.java new file mode 100644 index 000000000000..364ff5a5c68c --- /dev/null +++ b/src/main/java/address/parser/qualifier/CityQualifier.java @@ -0,0 +1,22 @@ +package address.parser.qualifier; + +import address.model.datatypes.person.ReadOnlyPerson; +import commons.StringUtil; + +public class CityQualifier implements Qualifier { + private String city; + + public CityQualifier(String city) { + this.city = city; + } + + @Override + public boolean run(ReadOnlyPerson person) { + return StringUtil.containsIgnoreCase(person.getCity(), city); + } + + @Override + public String toString() { + return "city=" + city; + } +} diff --git a/src/main/java/address/parser/qualifier/FirstNameQualifier.java b/src/main/java/address/parser/qualifier/FirstNameQualifier.java new file mode 100644 index 000000000000..22d8ae380c20 --- /dev/null +++ b/src/main/java/address/parser/qualifier/FirstNameQualifier.java @@ -0,0 +1,22 @@ +package address.parser.qualifier; + +import address.model.datatypes.person.ReadOnlyPerson; +import commons.StringUtil; + +public class FirstNameQualifier implements Qualifier { + private String firstName; + + public FirstNameQualifier(String firstName) { + this.firstName = firstName; + } + + @Override + public boolean run(ReadOnlyPerson person) { + return StringUtil.containsIgnoreCase(person.getFirstName(), firstName); + } + + @Override + public String toString() { + return "firstName=" + firstName; + } +} diff --git a/src/main/java/address/parser/qualifier/IdQualifier.java b/src/main/java/address/parser/qualifier/IdQualifier.java new file mode 100644 index 000000000000..e05f9c3124d5 --- /dev/null +++ b/src/main/java/address/parser/qualifier/IdQualifier.java @@ -0,0 +1,21 @@ +package address.parser.qualifier; + +import address.model.datatypes.person.ReadOnlyPerson; + +public class IdQualifier implements Qualifier { + private final int id; + + public IdQualifier(int id) { + this.id = id; + } + + @Override + public boolean run(ReadOnlyPerson person) { + return person.getId() == id; + } + + @Override + public String toString() { + return "id=" + id; + } +} diff --git a/src/main/java/address/parser/qualifier/LastNameQualifier.java b/src/main/java/address/parser/qualifier/LastNameQualifier.java new file mode 100644 index 000000000000..7e6906ac5b04 --- /dev/null +++ b/src/main/java/address/parser/qualifier/LastNameQualifier.java @@ -0,0 +1,22 @@ +package address.parser.qualifier; + +import address.model.datatypes.person.ReadOnlyPerson; +import commons.StringUtil; + +public class LastNameQualifier implements Qualifier { + private String lastName; + + public LastNameQualifier(String lastName) { + this.lastName = lastName; + } + + @Override + public boolean run(ReadOnlyPerson person) { + return StringUtil.containsIgnoreCase(person.getLastName(), lastName); + } + + @Override + public String toString() { + return "lastName=" + lastName; + } +} diff --git a/src/main/java/address/parser/qualifier/NameQualifier.java b/src/main/java/address/parser/qualifier/NameQualifier.java new file mode 100644 index 000000000000..d4d29468847a --- /dev/null +++ b/src/main/java/address/parser/qualifier/NameQualifier.java @@ -0,0 +1,24 @@ +package address.parser.qualifier; + +import address.model.datatypes.person.ReadOnlyPerson; + +public class NameQualifier implements Qualifier { + private Qualifier firstNameQualifier; + private Qualifier lastNameQualifier; + + public NameQualifier(String name) { + firstNameQualifier = new FirstNameQualifier(name); + lastNameQualifier = new LastNameQualifier(name); + } + + @Override + public boolean run(ReadOnlyPerson person) { + + return firstNameQualifier.run(person) || lastNameQualifier.run(person); + } + + @Override + public String toString() { + return firstNameQualifier + " OR " + lastNameQualifier; + } +} diff --git a/src/main/java/address/parser/qualifier/Qualifier.java b/src/main/java/address/parser/qualifier/Qualifier.java new file mode 100644 index 000000000000..69f6583e41e1 --- /dev/null +++ b/src/main/java/address/parser/qualifier/Qualifier.java @@ -0,0 +1,8 @@ +package address.parser.qualifier; + +import address.model.datatypes.person.ReadOnlyPerson; + +public interface Qualifier { + boolean run(ReadOnlyPerson person); + String toString(); +} diff --git a/src/main/java/address/parser/qualifier/StreetQualifier.java b/src/main/java/address/parser/qualifier/StreetQualifier.java new file mode 100644 index 000000000000..8e4e45df9377 --- /dev/null +++ b/src/main/java/address/parser/qualifier/StreetQualifier.java @@ -0,0 +1,22 @@ +package address.parser.qualifier; + +import address.model.datatypes.person.ReadOnlyPerson; +import commons.StringUtil; + +public class StreetQualifier implements Qualifier { + private final String street; + + public StreetQualifier(String street) { + this.street = street; + } + + @Override + public boolean run(ReadOnlyPerson person) { + return StringUtil.containsIgnoreCase(person.getStreet(), street); + } + + @Override + public String toString() { + return "street=" + street; + } +} diff --git a/src/main/java/address/parser/qualifier/TagQualifier.java b/src/main/java/address/parser/qualifier/TagQualifier.java new file mode 100644 index 000000000000..b36b089cad52 --- /dev/null +++ b/src/main/java/address/parser/qualifier/TagQualifier.java @@ -0,0 +1,25 @@ +package address.parser.qualifier; + +import address.model.datatypes.person.ReadOnlyPerson; +import commons.StringUtil; + +public class TagQualifier implements Qualifier { + private final String tagName; + + public TagQualifier(String tagName) { + this.tagName = tagName; + } + + @Override + public boolean run(ReadOnlyPerson person) { + return person.getTagList().stream() + .filter(tag -> StringUtil.containsIgnoreCase(tag.getName(), this.tagName)) + .findAny() + .isPresent(); + } + + @Override + public String toString() { + return "tag=" + tagName; + } +} diff --git a/src/main/java/address/parser/qualifier/TrueQualifier.java b/src/main/java/address/parser/qualifier/TrueQualifier.java new file mode 100644 index 000000000000..6e192738e3fa --- /dev/null +++ b/src/main/java/address/parser/qualifier/TrueQualifier.java @@ -0,0 +1,19 @@ +package address.parser.qualifier; + +import address.model.datatypes.person.ReadOnlyPerson; + +public class TrueQualifier implements Qualifier { + + public TrueQualifier() { + } + + @Override + public boolean run(ReadOnlyPerson person) { + return true; + } + + @Override + public String toString() { + return "TRUE"; + } +} diff --git a/src/main/java/address/storage/StorageAddressBook.java b/src/main/java/address/storage/StorageAddressBook.java new file mode 100644 index 000000000000..cdce8542470f --- /dev/null +++ b/src/main/java/address/storage/StorageAddressBook.java @@ -0,0 +1,53 @@ +package address.storage; + +import address.model.datatypes.ReadOnlyAddressBook; +import address.model.datatypes.person.ReadOnlyPerson; +import address.model.datatypes.tag.Tag; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Serialisable immutable address book class for local storage + */ +@XmlRootElement(name = "addressbook") +public class StorageAddressBook implements ReadOnlyAddressBook { + + @XmlElement + private List persons; + @XmlElement + private List tags; + + { + persons = new ArrayList<>(); + tags = new ArrayList<>(); + } + + /** + * for marshalling + */ + public StorageAddressBook() {} + + /** + * Conversion + */ + public StorageAddressBook(ReadOnlyAddressBook src) { + persons.addAll(src.getPersonList().stream().map(StoragePerson::new).collect(Collectors.toList())); + tags = src.getTagList(); + } + + @Override + public List getPersonList() { + return Collections.unmodifiableList(persons); + } + + @Override + public List getTagList() { + return Collections.unmodifiableList(tags); + } + +} diff --git a/src/main/java/address/storage/StorageManager.java b/src/main/java/address/storage/StorageManager.java new file mode 100644 index 000000000000..d88a7fbdda79 --- /dev/null +++ b/src/main/java/address/storage/StorageManager.java @@ -0,0 +1,246 @@ +package address.storage; + +import address.events.model.LocalModelChangedEvent; +import address.events.storage.*; +import address.exceptions.DataConversionException; +import address.main.ComponentManager; +import address.model.UserPrefs; +import address.model.datatypes.ReadOnlyAddressBook; +import address.util.AppLogger; +import address.util.Config; +import address.util.LoggerManager; +import com.google.common.eventbus.Subscribe; +import commons.FileUtil; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; +import java.util.function.Consumer; +import java.util.function.Supplier; + +/** + * Manages storage of addressbook data in local disk. + * Handles storage related events. + */ +public class StorageManager extends ComponentManager { + + private static final AppLogger logger = LoggerManager.getLogger(StorageManager.class); + private static final String DEFAULT_CONFIG_FILE = "config.json"; + private final Consumer loadedDataCallback; + private final Supplier defaultDataSupplier; + private UserPrefs userPrefs; + private File saveFile; + private File userPrefsFile; + + + public StorageManager(Consumer loadedDataCallback, + Supplier defaultDataSupplier, Config config, UserPrefs userPrefs) { + super(); + this.loadedDataCallback = loadedDataCallback; + this.defaultDataSupplier = defaultDataSupplier; + this.saveFile = new File(config.getLocalDataFilePath()); + this.userPrefsFile = config.getPrefsFileLocation(); + this.userPrefs = userPrefs; + } + + private static File getConfigFile(String configFilePath) { + if (configFilePath == null) return new File(DEFAULT_CONFIG_FILE); + return new File(configFilePath); + } + + public static Config getConfig(String configFilePath) { + File configFile = getConfigFile(configFilePath); + + Config config; + if (configFile.exists()) { + logger.info("Config file {} found, attempting to read.", configFile); + config = readFromConfigFile(configFile); + } else { + logger.info("Config file {} not found, using default config.", configFile); + config = new Config(); + } + // Recreate the file so that any missing fields will be restored + recreateFile(configFile, config); + return config; + } + + private static void recreateFile(File configFile, Config config) { + if (!deleteConfigFileIfExists(configFile)) return; + createAndWriteToConfigFile(configFile, config); + } + + private static void createAndWriteToConfigFile(File configFile, Config config) { + try { + FileUtil.serializeObjectToJsonFile(configFile, config); + } catch (IOException e) { + logger.warn("Error writing to config file {}.", configFile); + } + } + + /** + * Attempts to delete configFile if it exists + * + * @param configFile + * @return false if exception is thrown + */ + private static boolean deleteConfigFileIfExists(File configFile) { + if (!FileUtil.isFileExists(configFile)) return true; + + try { + FileUtil.deleteFile(configFile); + return true; + } catch (IOException e) { + logger.warn("Error removing previous config file {}.", configFile); + return false; + } + } + + /** + * Attempts to read config values from the given file + * + * @param configFile + * @return default config object if reading fails + */ + private static Config readFromConfigFile(File configFile) { + try { + return FileUtil.deserializeObjectFromJsonFile(configFile, Config.class); + } catch (IOException e) { + logger.warn("Error reading from config file {}: {}", configFile, e); + return new Config(); + } + } + + /** + * Raises a {@link FileOpeningExceptionEvent} if there was any problem in reading data from the file + * or if the file is not in the correct format. + */ + @Subscribe + public void handleLoadDataRequestEvent(LoadDataRequestEvent ldre) { + File dataFile = ldre.file; + logger.info("Handling load data request received: {}", dataFile); + loadDataFile(dataFile); + } + + /** + * Raises FileSavingExceptionEvent (similar to {@link #saveDataToFile(File, ReadOnlyAddressBook)}) + */ + @Subscribe + public void handleLocalModelChangedEvent(LocalModelChangedEvent lmce) { + logger.info("Local data changed, saving to primary data file"); + saveDataToFile(saveFile, lmce.data); + } + + /** + * Raises FileSavingExceptionEvent (similar to {@link #saveDataToFile(File, ReadOnlyAddressBook)}) + */ + @Subscribe + public void handleSaveDataRequestEvent(SaveDataRequestEvent sdre) { + logger.info("Save data request received: {}", sdre.data); + saveDataToFile(sdre.file, sdre.data); + } + + /** + * Creates the file if it is missing before saving. + * Raises FileSavingExceptionEvent if the file is not found or if there was an error during + * saving or data conversion. + */ + public void saveDataToFile(File file, ReadOnlyAddressBook data) { + try { + saveAddressBook(file, data); + } catch (IOException | DataConversionException e) { + raise(new FileSavingExceptionEvent(e, file)); + } + } + + /** + * Saves the address book data in the file specified. + */ + public static void saveAddressBook(File file, ReadOnlyAddressBook data) throws IOException, + DataConversionException { + FileUtil.createIfMissing(file); + XmlFileStorage.saveDataToFile(file, new StorageAddressBook(data)); + } + + /** + * Raises FileSavingExceptionEvent + */ + @Subscribe + public void handleSavePrefsRequestEvent(SavePrefsRequestEvent spre) { + logger.info("Save prefs request received: {}", spre.prefs); + savePrefsToFile(spre.prefs); + } + + /** + * Raises FileSavingExceptionEvent if there was an error during saving or data conversion. + */ + public void savePrefsToFile(UserPrefs prefs) { + try { + FileUtil.serializeObjectToJsonFile(userPrefsFile, prefs); + } catch (IOException e) { + raise(new FileSavingExceptionEvent(e, userPrefsFile)); + } + } + + public static UserPrefs getUserPrefs(File prefsFile) { + UserPrefs prefs = new UserPrefs(); + + if (!FileUtil.isFileExists(prefsFile)) { + return prefs; + } + + try { + logger.debug("Attempting to load prefs from file: {}", prefsFile); + prefs = FileUtil.deserializeObjectFromJsonFile(prefsFile, UserPrefs.class); + } catch (IOException e) { + logger.debug("Error loading prefs from file: {}", e); + } + + return prefs; + } + + /** + * Loads the data from the local data file (based on user preferences). + */ + public void start() { + logger.info("Starting storage manager."); + initializeDataFile(saveFile); + } + + protected void initializeDataFile(File dataFile) { + try { + loadDataFromFile(dataFile); + } catch (FileNotFoundException e) { + logger.debug("File {} not found, attempting to create file with default data", dataFile); + try { + saveAddressBook(saveFile, defaultDataSupplier.get()); + } catch (DataConversionException | IOException e1) { + logger.fatal("Unable to initialize local data file with default data."); + assert false : "Unable to initialize local data file with default data."; + } + } + } + + protected void loadDataFile(File dataFile) { + try { + loadDataFromFile(dataFile); + } catch (FileNotFoundException e) { + logger.debug("File not found: {}", dataFile); + raise(new FileOpeningExceptionEvent(e, dataFile)); + } + } + + protected void loadDataFromFile(File dataFile) throws FileNotFoundException { + try { + logger.debug("Attempting to load data from file: {}", dataFile); + loadedDataCallback.accept(getData()); + } catch (DataConversionException e) { + logger.debug("Error loading data from file: {}", e); + raise(new FileOpeningExceptionEvent(e, dataFile)); + } + } + + public ReadOnlyAddressBook getData() throws FileNotFoundException, DataConversionException { + logger.debug("Attempting to read data from file: {}", saveFile); + return XmlFileStorage.loadDataFromSaveFile(saveFile); + } +} diff --git a/src/main/java/address/storage/StoragePerson.java b/src/main/java/address/storage/StoragePerson.java new file mode 100644 index 000000000000..4d54411ead4a --- /dev/null +++ b/src/main/java/address/storage/StoragePerson.java @@ -0,0 +1,115 @@ +package address.storage; + +import address.model.datatypes.person.ReadOnlyPerson; +import address.model.datatypes.tag.Tag; +import commons.XmlUtil; + +import javax.xml.bind.annotation.XmlElement; +import javax.xml.bind.annotation.XmlRootElement; +import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +/** + * Serialisable person class for local storage + */ +@XmlRootElement(name = "person") +public class StoragePerson implements ReadOnlyPerson { + + @XmlElement + private int id; + @XmlElement + private String firstName; + @XmlElement + private String lastName; + + @XmlElement + private String githubUsername; + @XmlElement + private String street; + @XmlElement + private String postalCode; + @XmlElement + private String city; + + @XmlElement + @XmlJavaTypeAdapter(XmlUtil.LocalDateAdapter.class) + private LocalDate birthday; + @XmlElement + private List tags; + + { + tags = new ArrayList<>(); + } + + /** + * for xml/json marshalling + */ + public StoragePerson() {} + + /** + * for conversion + */ + public StoragePerson(ReadOnlyPerson src) { + id = src.getId(); + firstName = src.getFirstName(); + lastName = src.getLastName(); + githubUsername = src.getGithubUsername(); + street = src.getStreet(); + postalCode = src.getPostalCode(); + city = src.getCity(); + birthday = src.getBirthday(); + tags.addAll(src.getTagList()); + } + + @Override + public int getId() { + return id; + } + + @Override + public String getFirstName() { + return firstName; + } + + @Override + public String getLastName() { + return lastName; + } + + public String getGithubUsername() { + return githubUsername; + } + + @Override + public String getStreet() { + return street; + } + + @Override + public String getPostalCode() { + return postalCode; + } + + @Override + public String getCity() { + return city; + } + + @Override + public LocalDate getBirthday() { + return birthday; + } + + @Override + public List getTagList() { + return Collections.unmodifiableList(tags); + } + + @Override + public boolean hasName(String firstName, String lastName) { + return this.firstName.equals(firstName) && this.lastName.equals(lastName); + } +} diff --git a/src/main/java/address/storage/XmlFileStorage.java b/src/main/java/address/storage/XmlFileStorage.java new file mode 100644 index 000000000000..19526dd62764 --- /dev/null +++ b/src/main/java/address/storage/XmlFileStorage.java @@ -0,0 +1,38 @@ +package address.storage; + +import address.exceptions.DataConversionException; +import commons.XmlUtil; + +import javax.xml.bind.JAXBException; +import java.io.File; +import java.io.FileNotFoundException; + +/** + * Stores addressbook data in an XML file + */ +public class XmlFileStorage { + /** + * Saves the given addressbook data to the specified file. + */ + public static void saveDataToFile(File file, StorageAddressBook addressBook) + throws DataConversionException, FileNotFoundException { + try { + XmlUtil.saveDataToFile(file, addressBook); + } catch (JAXBException e) { + throw new DataConversionException(e); + } + } + + /** + * Returns address book in the file or an empty address book + */ + public static StorageAddressBook loadDataFromSaveFile(File file) throws DataConversionException, + FileNotFoundException { + try { + return XmlUtil.getDataFromFile(file, StorageAddressBook.class); + } catch (JAXBException e) { + throw new DataConversionException(e); + } + } + +} diff --git a/src/main/java/address/ui/PersonListViewCell.java b/src/main/java/address/ui/PersonListViewCell.java new file mode 100644 index 000000000000..b49dbbd187bf --- /dev/null +++ b/src/main/java/address/ui/PersonListViewCell.java @@ -0,0 +1,24 @@ +package address.ui; + +import address.controller.PersonCardController; +import address.model.datatypes.person.ReadOnlyPerson; +import javafx.scene.control.ListCell; + +public class PersonListViewCell extends ListCell { + + public PersonListViewCell() { + + } + + @Override + protected void updateItem(ReadOnlyPerson person, boolean empty) { + super.updateItem(person, empty); + + if (empty || person == null) { + setGraphic(null); + setText(null); + } else { + setGraphic(new PersonCardController(person).getLayout()); + } + } +} diff --git a/src/main/java/address/ui/Ui.java b/src/main/java/address/ui/Ui.java new file mode 100644 index 000000000000..dabdac56a9c3 --- /dev/null +++ b/src/main/java/address/ui/Ui.java @@ -0,0 +1,34 @@ +package address.ui; + +import address.MainApp; +import address.controller.MainController; +import address.model.ModelManager; +import address.model.UserPrefs; +import address.util.Config; +import address.util.GuiSettings; +import javafx.stage.Stage; + +/** + * The UI of the app. + */ +public class Ui { + private MainController mainController; + private UserPrefs pref; + + public Ui(MainApp mainApp, ModelManager modelManager, Config config, UserPrefs pref){ + mainController = new MainController(mainApp, modelManager, config, pref); + this.pref = pref; + } + + public void start(Stage primaryStage) { + mainController.start(primaryStage); + } + + public void stop() { + Stage stage = mainController.getPrimaryStage(); + GuiSettings guiSettings = new GuiSettings(stage.getWidth(), stage.getHeight(), + (int) stage.getX(), (int) stage.getY()); + pref.setGuiSettings(guiSettings); + mainController.stop(); + } +} diff --git a/src/main/java/address/util/AppLogger.java b/src/main/java/address/util/AppLogger.java new file mode 100644 index 000000000000..45217d2b5733 --- /dev/null +++ b/src/main/java/address/util/AppLogger.java @@ -0,0 +1,56 @@ +package address.util; + +import address.events.BaseEvent; +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.Logger; + +public class AppLogger { + private Logger logger; + + public AppLogger(Logger logger) { + this.logger = logger; + } + + public void info(String message, Object... params) { + logger.info(message, params); + } + + public void debug(String message, Object... params) { + logger.debug(message, params); + } + + public void warn(String message, Object... params) { + logger.warn(message, params); + + } + + public void fatal(String message, Object... params) { + logger.fatal(message, params); + } + + public void throwing(T throwable) { + logger.throwing(Level.DEBUG, throwable); + } + + public void catching(T throwable) { + logger.catching(Level.DEBUG, throwable); + } + + public void debugEvent(T event) { + debugEvent("{}: {}", event.getClass().getSimpleName(), event.toString()); + } + + public void infoEvent(BaseEvent event) { + infoEvent("{}: {}", event.getClass().getSimpleName(), event.toString()); + } + + // this method is required since debug(message, obj, obj) seems to be problematic + private void debugEvent(String message, Object... params) { + logger.debug(message, params); + } + + // this method is required since info(message, obj, obj) seems to be problematic + private void infoEvent(String message, Object... params) { + logger.info(message, params); + } +} diff --git a/src/main/java/address/util/Config.java b/src/main/java/address/util/Config.java new file mode 100644 index 000000000000..09b706d6b27f --- /dev/null +++ b/src/main/java/address/util/Config.java @@ -0,0 +1,80 @@ +package address.util; + +import org.apache.logging.log4j.Level; + +import java.io.File; +import java.util.HashMap; + +/** + * Config values used by the app + */ +public class Config { + // Default values + private static final Level DEFAULT_LOGGING_LEVEL = Level.INFO; + private static final HashMap DEFAULT_SPECIAL_LOG_LEVELS = new HashMap<>(); + private static final String DEFAULT_LOCAL_DATA_FILE_PATH = "data/addressbook.xml"; + private static final String DEFAULT_ADDRESS_BOOK_NAME = "MyAddressBook"; + + // Config values + private String appTitle = "Address App"; + // Customizable through config file + private Level currentLogLevel = DEFAULT_LOGGING_LEVEL; + private HashMap specialLogLevels = DEFAULT_SPECIAL_LOG_LEVELS; + private File prefsFileLocation = new File("preferences.json"); //Default user preferences file + private String localDataFilePath = DEFAULT_LOCAL_DATA_FILE_PATH; + private String addressBookName = DEFAULT_ADDRESS_BOOK_NAME; + + + public Config() { + } + + public String getAppTitle() { + return appTitle; + } + + public void setAppTitle(String appTitle) { + this.appTitle = appTitle; + } + + public Level getCurrentLogLevel() { + return currentLogLevel; + } + + public void setCurrentLogLevel(Level currentLogLevel) { + this.currentLogLevel = currentLogLevel; + } + + public HashMap getSpecialLogLevels() { + return specialLogLevels; + } + + public void setSpecialLogLevels(HashMap specialLogLevels) { + this.specialLogLevels = specialLogLevels; + } + + public File getPrefsFileLocation() { + return prefsFileLocation; + } + + public void setPrefsFileLocation(File prefsFileLocation) { + this.prefsFileLocation = prefsFileLocation; + } + + public String getLocalDataFilePath() { + return localDataFilePath; + } + + public void setLocalDataFilePath(String localDataFilePath) { + this.localDataFilePath = localDataFilePath; + } + + public String getAddressBookName() { + return addressBookName; + } + + public void setAddressBookName(String addressBookName) { + this.addressBookName = addressBookName; + } + + +} diff --git a/src/main/java/address/util/DependencyChecker.java b/src/main/java/address/util/DependencyChecker.java new file mode 100644 index 000000000000..ee1950eea178 --- /dev/null +++ b/src/main/java/address/util/DependencyChecker.java @@ -0,0 +1,139 @@ +package address.util; + +import address.MainApp; +import address.exceptions.DependencyCheckException; +import commons.FileUtil; +import commons.JsonUtil; +import commons.VersionData; + +import javax.swing.*; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.Optional; +import java.util.stream.Collectors; + +/** + * Checks if all required dependencies are present + */ +public class DependencyChecker { + + private static final AppLogger logger = LoggerManager.getLogger(DependencyChecker.class); + private final String requiredJavaVersionString; + private Runnable quitApp; + + public DependencyChecker(String requiredJavaVersionString, Runnable quitApp) { + this.requiredJavaVersionString = requiredJavaVersionString; + this.quitApp = quitApp; + } + + public void verify() { + if (!ManifestFileReader.isRunFromJar()) { + logger.info("Not run from Jar, skipping dependencies check"); + return; + } + logger.info("Verifying dependencies"); + + try { + checkJavaVersionDependency(requiredJavaVersionString); + } catch (DependencyCheckException e) { + showErrorDialogAndQuit("Java Version Check Failed", "There are missing dependencies", e.getMessage()); + } + + try { + checkLibrariesDependency(); + } catch (DependencyCheckException e) { + showErrorDialogAndQuit("Libraries Check Failed", "Your Java version is not compatible", e.getMessage()); + } + + logger.info("All dependencies present"); + } + + public void checkJavaVersionDependency(String javaVersion) throws DependencyCheckException { + logger.info("Verifying java version dependency"); + + JavaVersion requiredVersion; + try { + requiredVersion = JavaVersion.fromString(javaVersion); + } catch (IllegalArgumentException e) { + logger.warn("Required Java Version string cannot be parsed. This should have been covered by test."); + assert false; + return; + } + + JavaVersion runtimeVersion; + String javaRuntimeVersionString = System.getProperty("java.runtime.version"); + try { + runtimeVersion = JavaVersion.fromString(javaRuntimeVersionString); + } catch (IllegalArgumentException e) { + throw new DependencyCheckException(String.format( + "Java runtime version (%s) is not known and may not be compatible with this app.", + javaRuntimeVersionString)); + } + + if (JavaVersion.isJavaVersionLower(runtimeVersion, requiredVersion)) { + throw new DependencyCheckException(String.format( + "Your Java Version (%s) is lower than this app's requirement (%s). Please update your Java.", + runtimeVersion, requiredVersion)); + } + } + + public void checkLibrariesDependency() throws DependencyCheckException { + logger.info("Verifying dependency libraries are present"); + + List missingDependencies = getMissingDependencies(); + + if (!missingDependencies.isEmpty()) { + StringBuilder message = new StringBuilder("Missing dependencies:\n"); + for (String missingDependency : missingDependencies) { + message.append("- ").append(missingDependency).append("\n"); + } + String missingDependenciesMessage = message.toString().trim(); + logger.warn(missingDependenciesMessage); + + throw new DependencyCheckException(missingDependenciesMessage); + } + } + + public List getMissingDependencies() { + Optional> dependenciesWrapper = ManifestFileReader.getLibrariesInClasspathFromManifest(); + + if (!dependenciesWrapper.isPresent()) { + logger.info("Dependencies not present - not running check as this indicates not run from JAR"); + return new ArrayList<>(); + } + + List dependencies = dependenciesWrapper.get(); + + excludePlatformSpecificDependencies(dependencies); + + return dependencies.stream() + .filter(dependency -> !FileUtil.isFileExists(dependency)).collect(Collectors.toList()); + } + + private void excludePlatformSpecificDependencies(List dependencies) { + String json = FileUtil.readFromInputStream(MainApp.class.getResourceAsStream("/VersionData.json")); + + commons.VersionData versionData; + + try { + versionData = JsonUtil.fromJsonString(json, VersionData.class); + } catch (IOException e) { + logger.warn("Failed to parse JSON data to process platform specific dependencies", e); + return; + } + + List librariesNotForCurrentMachine = versionData.getLibraries().stream() + .filter(libDesc -> libDesc.getOs() != commons.OsDetector.Os.ANY + && libDesc.getOs() != commons.OsDetector.getOs()) + .map(libDesc -> "lib/" + libDesc.getFileName()) + .collect(Collectors.toList()); + + dependencies.removeAll(librariesNotForCurrentMachine); + } + + public void showErrorDialogAndQuit(String title, String headerText, String contentText) { + JOptionPane.showMessageDialog(null, headerText + "\n\n" + contentText, title, JOptionPane.ERROR_MESSAGE); + quitApp.run(); + } +} diff --git a/src/main/java/address/util/GuiSettings.java b/src/main/java/address/util/GuiSettings.java new file mode 100644 index 000000000000..080141c84561 --- /dev/null +++ b/src/main/java/address/util/GuiSettings.java @@ -0,0 +1,41 @@ +package address.util; + +import java.awt.*; +import java.io.Serializable; + +/** + * A Serializable class that contains the GUI settings. + */ +public class GuiSettings implements Serializable { + + private static final double DEFAULT_HEIGHT = 600; + private static final double DEFAULT_WIDTH = 740; + + private Double windowWidth; + private Double windowHeight; + private Point windowCoordinates; + + public GuiSettings() { + this.windowWidth = DEFAULT_WIDTH; + this.windowHeight = DEFAULT_HEIGHT; + this.windowCoordinates = null; // null represent no coordinates + } + + public GuiSettings(Double windowWidth, Double windowHeight, int xPosition, int yPosition) { + this.windowWidth = windowWidth; + this.windowHeight = windowHeight; + this.windowCoordinates = new Point(xPosition, yPosition); + } + + public Double getWindowWidth() { + return windowWidth; + } + + public Double getWindowHeight() { + return windowHeight; + } + + public Point getWindowCoordinates() { + return windowCoordinates; + } +} diff --git a/src/main/java/address/util/JavaVersion.java b/src/main/java/address/util/JavaVersion.java new file mode 100644 index 000000000000..aa4f60e94b5b --- /dev/null +++ b/src/main/java/address/util/JavaVersion.java @@ -0,0 +1,82 @@ +package address.util; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents Java version + */ +public class JavaVersion implements Comparable { + + private static final Pattern JAVA_8_VERSION_PATTERN = + Pattern.compile("(\\d+)\\.(\\d+)\\.(\\d+)\\_(\\d+)(-b(\\d+))?"); + + private static final String EXCEPTION_STRING_NOT_JAVA_VERSION = "String is not a valid Java Version. %s"; + + /** + * Java version in String is formatted as ".._" with optional suffix of + * "-b". Discard is not used because all Java versions (up to Java 8) will start with "1.". + * This might change in Java 9. + */ + private final int discard, major, minor, update, build; + + public JavaVersion(int discard, int major, int minor, int update, int build) { + this.discard = discard; + this.major = major; + this.minor = minor; + this.update = update; + this.build = build; + } + + public static JavaVersion fromString(String javaVersion) throws IllegalArgumentException { + Matcher javaVersionMatcher = JAVA_8_VERSION_PATTERN.matcher(javaVersion); + + if (!javaVersionMatcher.find()) { + throw new IllegalArgumentException(String.format(EXCEPTION_STRING_NOT_JAVA_VERSION, javaVersion)); + } + + return new JavaVersion(Integer.parseInt(javaVersionMatcher.group(1)), + Integer.parseInt(javaVersionMatcher.group(2)), + Integer.parseInt(javaVersionMatcher.group(3)), + Integer.parseInt(javaVersionMatcher.group(4)), + Integer.parseInt((javaVersionMatcher.group(6) != null ? javaVersionMatcher.group(6) + : "0"))); + } + + public String toString() { + return String.format("%d.%d.%d_%d-b%d", discard, major, minor, update, build); + } + + @Override + public int compareTo(JavaVersion other) { + return this.discard != other.discard ? this.discard - other.discard : + this.major != other.major ? this.major - other.major : + this.minor != other.minor ? this.minor - other.minor : + this.update != other.update ? this.update - other.update : + this.build != other.build ? this.build - other.build : 0; + } + + @Override + public int hashCode() { + String hash = String.format("%1$d%2$d%3$d%4$03d%5$03d", discard, major, minor, update, build); + + return Integer.parseInt(hash); + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof JavaVersion)) { + return false; + } + final JavaVersion other = (JavaVersion) obj; + + return compareTo(other) == 0; + } + + public static boolean isJavaVersionLower(JavaVersion runtime, JavaVersion required) { + return runtime.compareTo(required) < 0; + } +} diff --git a/src/main/java/address/util/LoggerManager.java b/src/main/java/address/util/LoggerManager.java new file mode 100644 index 000000000000..5764ace9defd --- /dev/null +++ b/src/main/java/address/util/LoggerManager.java @@ -0,0 +1,81 @@ +package address.util; + +import org.apache.logging.log4j.Level; +import org.apache.logging.log4j.LogManager; +import org.apache.logging.log4j.core.LoggerContext; +import org.apache.logging.log4j.core.config.AbstractConfiguration; +import org.apache.logging.log4j.core.config.LoggerConfig; + +import java.util.HashMap; + +public class LoggerManager { + private static final AppLogger logger = LoggerManager.getLogger(LoggerManager.class); + private static Level currentLogLevel = Level.INFO; + private static HashMap specialLogLevels = new HashMap<>(); + + public static void init(Config config) { + currentLogLevel = config.getCurrentLogLevel(); + specialLogLevels = config.getSpecialLogLevels(); + + logger.info("currentLogLevel: {}", currentLogLevel); + logger.info("specialLogLevels: {}", specialLogLevels); + + LoggerContext loggerContext = getLoggerContext(); + AbstractConfiguration loggersConfig = getLoggersConfig(loggerContext); + updateExistingLoggersLevel(loggersConfig); + loggerContext.updateLoggers(loggersConfig); + } + + public static AppLogger getLogger(String className, Level loggingLevel) { + setClassLoggingLevel(className, loggingLevel); + return new AppLogger(LogManager.getLogger(className)); + } + + public static AppLogger getLogger(String className) { + Level loggingLevelToSet = determineLoggingLevelToSet(className); + setClassLoggingLevel(className, loggingLevelToSet); + return new AppLogger(LogManager.getLogger(className)); + } + + public static AppLogger getLogger(Class clazz) { + if (clazz == null) return new AppLogger(LogManager.getRootLogger()); + return getLogger(clazz.getSimpleName()); + } + + private static LoggerContext getLoggerContext() { + return (LoggerContext) LogManager.getContext(false); + } + + private static AbstractConfiguration getLoggersConfig(LoggerContext loggerContext) { + return (AbstractConfiguration) loggerContext.getConfiguration(); + } + + private static void updateExistingLoggersLevel(AbstractConfiguration absConfig) { + absConfig.getLoggers().forEach((loggerName, loggerConfig) -> { + loggerConfig.setLevel(determineLoggingLevelToSet(loggerName)); + }); + } + + private static void setClassLoggingLevel(String className, Level loggingLevel) { + LoggerContext loggerContext = getLoggerContext(); + AbstractConfiguration loggersConfig = getLoggersConfig(loggerContext); + setLoggingLevel(loggersConfig, className, loggingLevel); + loggerContext.updateLoggers(loggersConfig); + } + + private static Level determineLoggingLevelToSet(String className) { + if (specialLogLevels != null && specialLogLevels.containsKey(className)) { + return specialLogLevels.get(className); + } + return currentLogLevel; + } + + private static void setLoggingLevel(AbstractConfiguration config, String className, Level loggingLevel) { + if (config.getLogger(className) != null) { + config.getLogger(className).setLevel(loggingLevel); + return; + } + + config.addLogger(className, new LoggerConfig(className, loggingLevel, true)); + } +} diff --git a/src/main/java/address/util/ManifestFileReader.java b/src/main/java/address/util/ManifestFileReader.java new file mode 100644 index 000000000000..b07c63111978 --- /dev/null +++ b/src/main/java/address/util/ManifestFileReader.java @@ -0,0 +1,73 @@ +package address.util; + +import address.MainApp; + +import java.io.IOException; +import java.net.URL; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.Optional; +import java.util.jar.Attributes; +import java.util.jar.Manifest; + +/** + * Reads values in manifest file + */ +public class ManifestFileReader { + private static final AppLogger logger = LoggerManager.getLogger(ManifestFileReader.class); + + private static String getResourcePath() { + Class mainAppClass = MainApp.class; + String className = mainAppClass.getSimpleName() + ".class"; + return mainAppClass.getResource(className).toString(); + } + + public static boolean isRunFromJar() { + String resourcePath = getResourcePath(); + return resourcePath.startsWith("jar"); + + } + + private static String getManifestPath() { + String resourcePath = getResourcePath(); + + return resourcePath.substring(0, resourcePath.lastIndexOf("!") + 1) + "/META-INF/MANIFEST.MF"; + } + + private static Optional getManifest() { + if (!isRunFromJar()) { + return Optional.empty(); + } + + String manifestPath = getManifestPath(); + + Manifest manifest; + + try { + manifest = new Manifest(new URL(manifestPath).openStream()); + } catch (IOException e) { + logger.debug("Manifest can't be read, most probably not run from JAR", e); + return Optional.empty(); + } + + return Optional.of(manifest); + } + + /** + * @return the libraries as mentioned in manifest classpath + */ + public static Optional> getLibrariesInClasspathFromManifest() { + Optional manifest = getManifest(); + + if (!manifest.isPresent()) { + logger.debug("Manifest is not present"); + + return Optional.of(new ArrayList<>()); + } + + Attributes attr = manifest.get().getMainAttributes(); + + return Optional.of(new ArrayList<>(Arrays.asList(attr.getValue("Class-path").split("\\s+")))); + } +} diff --git a/src/main/java/address/util/collections/FilteredList.java b/src/main/java/address/util/collections/FilteredList.java new file mode 100644 index 000000000000..12cd9ef462ee --- /dev/null +++ b/src/main/java/address/util/collections/FilteredList.java @@ -0,0 +1,133 @@ +package address.util.collections; + +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.TransformationList; + +import java.util.function.Predicate; +import java.util.stream.Collectors; + +/** + * This class is meant to handle filtering the given list of elements given a predicate + * + * Elements not affected by the filter change will not be sent in the changes, unlike + * javafx.collections.transformation.FilteredList which removes all elements and adds the matching ones back + */ +public class FilteredList extends TransformationList { + private ObservableList sourceCopy; + private Predicate predicate; + private ObservableList filteredList; + + public FilteredList(ObservableList source, Predicate predicate) { + this(source); + setPredicate(predicate); + } + + public FilteredList(ObservableList source) { + super(source); + this.sourceCopy = FXCollections.observableArrayList(source); + this.filteredList = FXCollections.observableArrayList(source); + } + + @Override + public E get(int index) { + return filteredList.get(index); + } + + @Override + public int size() { + return filteredList.size(); + } + + private ObservableList filterList(Predicate predicate) { + return sourceCopy.stream() + .filter(predicate) + .collect(Collectors.toCollection(FXCollections::observableArrayList)); + } + + /** + * Sets the predicate filter for the list + * + * @param predicate should not be null + */ + public void setPredicate(Predicate predicate) { + this.predicate = predicate; + ObservableList newFilteredList = filterList(predicate); + + ObservableList removedList = getRemovedList(filteredList, newFilteredList); + ObservableList addedList = getAddedList(filteredList, newFilteredList); + + beginChange(); + fireRemoveChanges(removedList, filteredList); + fireAddChanges(addedList, filteredList); + endChange(); + } + + private ObservableList getAddedList(ObservableList oldList, ObservableList newList) { + return sourceCopy.stream() + .filter(e -> !oldList.contains(e)) + .filter(newList::contains) + .collect(Collectors.toCollection(FXCollections::observableArrayList)); + } + + private ObservableList getRemovedList(ObservableList oldList, ObservableList newList) { + return getAddedList(newList, oldList); + } + + /** + * Fires add change notifications for any observers + * + * @param addedList list of added elements + * @param updatedList list after the add changes have been made + */ + private void fireAddChanges(ObservableList addedList, ObservableList updatedList) { + addedList.forEach(e -> { + updatedList.add(e); + nextAdd(updatedList.indexOf(e), updatedList.indexOf(e) + 1); + }); + } + + /** + * Fires remove change notifications for any observers + * + * @param removedList list of removed elements + * @param originalList list before the remove changes have been made + */ + private void fireRemoveChanges(ObservableList removedList, ObservableList originalList) { + removedList.forEach(e -> { + originalList.remove(e); + nextRemove(originalList.indexOf(e), e); + }); + } + + @Override + protected void sourceChanged(ListChangeListener.Change c) { + beginChange(); + while (c.next()) { + if (c.wasAdded() || c.wasRemoved()) { + sourceCopy.removeAll(c.getRemoved()); + c.getRemoved().stream() + .filter(predicate) + .forEach(e -> { + filteredList.remove(e); + nextRemove(filteredList.indexOf(e), e); + }); + + sourceCopy.addAll(c.getAddedSubList()); + c.getAddedSubList().stream() + .filter(predicate) + .forEach(e -> { + filteredList.add(e); + nextAdd(filteredList.indexOf(e), filteredList.indexOf(e) + 1); + }); + } + } + endChange(); + } + + @Override + public int getSourceIndex(int index) { + return sourceCopy.indexOf(filteredList.get(index)); + } +} diff --git a/src/main/java/address/util/collections/UnmodifiableObservableList.java b/src/main/java/address/util/collections/UnmodifiableObservableList.java new file mode 100644 index 000000000000..5b9d59915cfe --- /dev/null +++ b/src/main/java/address/util/collections/UnmodifiableObservableList.java @@ -0,0 +1,325 @@ +package address.util.collections; + +import javafx.beans.InvalidationListener; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.FilteredList; +import javafx.collections.transformation.SortedList; + +import java.text.Collator; +import java.util.*; +import java.util.function.Consumer; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; +import java.util.stream.Stream; + +/** + * Unmodifiable view of an observable list + */ +public class UnmodifiableObservableList implements ObservableList { + + public static final String MUTATION_OP_EXCEPTION_MESSAGE = "Attempted to modify an unmodifiable view"; + + private final ObservableList backingList; + + + public UnmodifiableObservableList(ObservableList backingList) { + if (backingList == null) { + throw new NullPointerException(); + } + this.backingList = backingList; + } + + + @Override + public final void addListener(ListChangeListener listener) { + backingList.addListener(listener); + } + + @Override + public final void removeListener(ListChangeListener listener) { + backingList.removeListener(listener); + } + + @Override + public final void addListener(InvalidationListener listener) { + backingList.addListener(listener); + } + + @Override + public final void removeListener(InvalidationListener listener) { + backingList.removeListener(listener); + } + + + @Override + public final boolean addAll(Object... elements) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final boolean setAll(Object... elements) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final boolean setAll(Collection col) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final boolean removeAll(Object... elements) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final boolean retainAll(Object... elements) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final void remove(int from, int to) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + + @Override + public final FilteredList filtered(Predicate predicate) { + return new FilteredList<>(this, predicate); + } + + @Override + public final SortedList sorted(Comparator comparator) { + return new SortedList<>(this, comparator); + } + + @Override + public final SortedList sorted() { + return sorted(Comparator.nullsFirst((o1, o2) -> { + if (o1 instanceof Comparable) { + return ((Comparable) o1).compareTo(o2); + } + return Collator.getInstance().compare(o1.toString(), o2.toString()); + })); + } + + + @Override + public final int size() { + return backingList.size(); + } + + @Override + public final boolean isEmpty() { + return backingList.isEmpty(); + } + + @Override + public final boolean contains(Object o) { + return backingList.contains(o); + } + + @Override + public final Iterator iterator() { + return new Iterator() { + private final Iterator i = backingList.iterator(); + + public final boolean hasNext() { + return i.hasNext(); + } + public final E next() { + return i.next(); + } + public final void remove() { + throw new UnsupportedOperationException(); + } + @Override + public final void forEachRemaining(Consumer action) { + // Use backing collection version + i.forEachRemaining(action); + } + }; + } + + @Override + public final Object[] toArray() { + return backingList.toArray(); + } + + @Override + public final T[] toArray(T[] a) { + return backingList.toArray(a); + } + + + @Override + public final boolean add(E o) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final boolean remove(Object o) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final boolean containsAll(Collection c) { + return backingList.containsAll(c); + } + + @Override + public final boolean addAll(Collection c) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final boolean addAll(int index, Collection c) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final boolean removeAll(Collection c) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final boolean retainAll(Collection c) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final void replaceAll(UnaryOperator operator) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final void sort(Comparator c) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final void clear() { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + + @Override + public final boolean equals(Object o) { + return o == this || backingList.equals(o); + } + + @Override + public final int hashCode() { + return backingList.hashCode(); + } + + + @Override + public final E get(int index) { + return backingList.get(index); + } + + @Override + public final Object set(int index, Object element) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final void add(int index, Object element) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final E remove(int index) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final int indexOf(Object o) { + return backingList.indexOf(o); + } + + @Override + public final int lastIndexOf(Object o) { + return backingList.lastIndexOf(o); + } + + @Override + public final ListIterator listIterator() { + return listIterator(0); + } + + @Override + public final ListIterator listIterator(int index) { + return new ListIterator() { + private final ListIterator i = backingList.listIterator(index); + + public final boolean hasNext() { + return i.hasNext(); + } + public final E next() { + return i.next(); + } + public final boolean hasPrevious() { + return i.hasPrevious(); + } + public final E previous() { + return i.previous(); + } + public final int nextIndex() { + return i.nextIndex(); + } + public final int previousIndex() { + return i.previousIndex(); + } + + public final void remove() { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + public final void set(E e) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + public final void add(E e) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @Override + public final void forEachRemaining(Consumer action) { + i.forEachRemaining(action); + } + }; + } + + @Override + public final List subList(int fromIndex, int toIndex) { + return Collections.unmodifiableList(backingList.subList(fromIndex, toIndex)); + } + + @SuppressWarnings("unchecked") + @Override + public final Spliterator spliterator() { + return (Spliterator) backingList.spliterator(); + } + + @Override + public final boolean removeIf(Predicate filter) { + throw new UnsupportedOperationException(MUTATION_OP_EXCEPTION_MESSAGE); + } + + @SuppressWarnings("unchecked") + @Override + public final Stream stream() { + return (Stream) backingList.stream(); + } + + @SuppressWarnings("unchecked") + @Override + public final Stream parallelStream() { + return (Stream) backingList.parallelStream(); + } + + @Override + public final void forEach(Consumer action) { + backingList.forEach(action); + } +} diff --git a/src/main/java/commons/DateTimeUtil.java b/src/main/java/commons/DateTimeUtil.java new file mode 100644 index 000000000000..8d35c6b77a49 --- /dev/null +++ b/src/main/java/commons/DateTimeUtil.java @@ -0,0 +1,71 @@ +package commons; + +import java.time.LocalDate; +import java.time.format.DateTimeFormatter; +import java.time.format.DateTimeParseException; + +/** + * Helper functions for handling dates. + * + * @author Marco Jakob + */ +public class DateTimeUtil { + + /** The date pattern that is used for conversion. Change as you wish. */ + private static final String DATE_PATTERN = "dd.MM.yyyy"; + + /** The date formatter. */ + private static final DateTimeFormatter DATE_FORMATTER = + DateTimeFormatter.ofPattern(DATE_PATTERN); + + /** + * Returns the given date as a well formatted String. The above defined + * {@link DateTimeUtil#DATE_PATTERN} is used. + * + * @param date the date to be returned as a string + * @return formatted string + */ + public static String format(LocalDate date) { + if (date == null) { + return null; + } + return DATE_FORMATTER.format(date); + } + + /** + * Converts a String in the format of the defined {@link DateTimeUtil#DATE_PATTERN} + * to a {@link LocalDate} object. + * + * Returns null if the String could not be converted. + * + * @param dateString the date as String + * @return the date object or null if it could not be converted + */ + public static LocalDate parse(String dateString) { + try { + return DATE_FORMATTER.parse(dateString, LocalDate::from); + } catch (DateTimeParseException e) { + return null; + } + } + + /** + * Checks the String whether it is a valid date. + * + * @param dateString + * @return true if the String is a valid date + */ + public static boolean validDate(String dateString) { + // Try to parse the String. + return DateTimeUtil.parse(dateString) != null; + } + + /** + * Converts millisecs to secs (Round down). + * @param millisecs + * @return + */ + public static long millisecsToSecs(long millisecs){ + return millisecs / 1000; + } +} diff --git a/src/main/java/commons/FileUtil.java b/src/main/java/commons/FileUtil.java new file mode 100644 index 000000000000..ddf888fd7e21 --- /dev/null +++ b/src/main/java/commons/FileUtil.java @@ -0,0 +1,229 @@ +package commons; + +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.net.URISyntaxException; +import java.nio.file.*; +import java.util.ArrayList; +import java.util.List; + +/** + * Writes and reads file + */ +public class FileUtil { + private static final String CHARSET = "UTF-8"; + + public static boolean isFileExists(File file) { + return file.exists() && file.isFile(); + } + + public static boolean isFileExists(String filepath) { + return isFileExists(new File(filepath)); + } + + public static boolean isDirExists(File dir) { + return dir.exists() && dir.isDirectory(); + } + + public static void createIfMissing(File file) throws IOException { + if (!isFileExists(file)) { + createFile(file); + } + } + + /** + * Creates a file if it does not exist along with its missing parent directories + * + * @return true if file is created, false if file already exists + */ + public static boolean createFile(File file) throws IOException { + if (file.exists()) { + return false; + } + + createParentDirsOfFile(file); + + return file.createNewFile(); + } + + public static void deleteFile(String filepath) throws IOException { + deleteFile(new File(filepath)); + } + + public static void deleteFile(File file) throws IOException { + Files.delete(file.toPath()); + } + + public static void deleteFileIfExists(String filepath) throws IOException { + deleteFileIfExists(new File(filepath)); + } + + public static void deleteFileIfExists(File file) throws IOException { + if (!isFileExists(file)) { + return; + } + + deleteFile(file); + } + + /** + * Lists all files in directory and its subdirectories + */ + public static List listFilesInDir(Path directory) { + List filepaths = new ArrayList<>(); + + try (DirectoryStream stream = Files.newDirectoryStream(directory)) { + for (Path path : stream) { + if (path.toFile().isDirectory()) { + filepaths.addAll(listFilesInDir(path)); + } else { + filepaths.add(path.toFile()); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + return filepaths; + } + + /** + * Creates the given directory along with its parent directories + * + * @param dir the directory to be created; assumed not null + * @throws IOException if the directory or a parent directory cannot be created + */ + public static void createDirs(File dir) throws IOException { + if (!dir.exists() && !dir.mkdirs()) { + throw new IOException("Failed to make directories of " + dir.getName()); + } + } + + /** + * Creates parent directories of file if it has a parent directory + */ + public static void createParentDirsOfFile(File file) throws IOException { + File parentDir = file.getParentFile(); + + if (parentDir != null) { + createDirs(parentDir); + } + } + + /** + * Move file from source to dest + * @param isOverwrite set true to overwrite source + */ + public static void moveFile(Path source, Path dest, boolean isOverwrite) throws IOException { + if (isOverwrite) { + Files.move(source, dest, StandardCopyOption.REPLACE_EXISTING); + } else { + Files.move(source, dest); + } + } + + /** + * + * @param source + * @param dest + * @param isOverwrite + * @throws IOException + */ + public static void copyFile(Path source, Path dest, boolean isOverwrite) throws IOException { + if (isOverwrite) { + Files.copy(source, dest, StandardCopyOption.REPLACE_EXISTING); + } else { + Files.copy(source, dest); + } + } + + /** + * @return List of filenames failed to be moved + */ + public static List moveContentOfADirectoryToAnother(String sourceDir, String targetDir) { + List sourceFiles = FileUtil.listFilesInDir(Paths.get(sourceDir)); + List failedToMoveFiles = new ArrayList<>(); + + for (File sourceFile : sourceFiles) { + Path sourceFilePath = sourceFile.toPath(); + Path targetFilePath = Paths.get(targetDir + File.separator + sourceFile.getName()); + + try { + FileUtil.moveFile(sourceFilePath, targetFilePath, true); + } catch (IOException e) { + e.printStackTrace(); + failedToMoveFiles.add(sourceFile.getPath()); + } + } + + return failedToMoveFiles; + } + + /** + * Assumes file exists + */ + public static String readFromFile(File file) throws IOException { + return new String(Files.readAllBytes(file.toPath()), CHARSET); + } + + /** + * Writes the content of a stream into a file + * + * Overwrites any existing file found at filePath + * @param in + * @param filePath + * @throws IOException + */ + public static void writeStreamIntoFile(InputStream in, Path filePath) throws IOException { + Files.copy(in, filePath, StandardCopyOption.REPLACE_EXISTING); + } + + /** + * Writes given string to a file. + * Will create the file if it does not exist yet. + */ + public static void writeToFile(File file, String content) throws IOException { + Files.write(file.toPath(), content.getBytes(CHARSET)); + } + + public static String readFromInputStream(InputStream inputStream) { + java.util.Scanner s = new java.util.Scanner(inputStream).useDelimiter("\\A"); + return s.hasNext() ? s.next() : ""; + } + + public static File getJarFileOfClass(Class givenClass) throws URISyntaxException { + return new File(givenClass.getProtectionDomain().getCodeSource().getLocation().toURI().getPath()); + } + + /** + * Converts a string to a platform-specific file path + * @param pathWithForwardSlash A String representing a file path but using '/' as the separator + * @return {@code pathWithForwardSlash} but '/' replaced with {@code File.separator} + */ + public static String getPath(String pathWithForwardSlash) { + assert pathWithForwardSlash != null; + assert pathWithForwardSlash.contains("/"); + return pathWithForwardSlash.replace("/", File.separator); + } + + /** + * Gets the file name from the given file path, assuming that + * path components are '/'-separated + * + * @param filePath should not be null + * @return + */ + public static String getFileName(String filePath) { + String[] pathComponents = filePath.split("/"); + return pathComponents[pathComponents.length - 1]; + } + + public static void serializeObjectToJsonFile(File jsonFile, T objectToSerialize) throws IOException { + FileUtil.writeToFile(jsonFile, JsonUtil.toJsonString(objectToSerialize)); + } + + public static T deserializeObjectFromJsonFile(File jsonFile, Class classOfObjectToDeserialize) + throws IOException { + return JsonUtil.fromJsonString(FileUtil.readFromFile(jsonFile), classOfObjectToDeserialize); + } +} diff --git a/src/main/java/commons/FxViewUtil.java b/src/main/java/commons/FxViewUtil.java new file mode 100644 index 000000000000..ece398130d68 --- /dev/null +++ b/src/main/java/commons/FxViewUtil.java @@ -0,0 +1,17 @@ +package commons; + +import javafx.scene.Node; +import javafx.scene.layout.AnchorPane; + +/** + * Contains utility methods for JavaFX views + */ +public class FxViewUtil { + + public static void applyAnchorBoundaryParameters(Node node, double left, double right, double top, double bottom) { + AnchorPane.setBottomAnchor(node, bottom); + AnchorPane.setLeftAnchor(node, left); + AnchorPane.setRightAnchor(node, right); + AnchorPane.setTopAnchor(node, top); + } +} diff --git a/src/main/java/commons/JsonUtil.java b/src/main/java/commons/JsonUtil.java new file mode 100644 index 000000000000..701f14711b44 --- /dev/null +++ b/src/main/java/commons/JsonUtil.java @@ -0,0 +1,99 @@ +package commons; + +import com.fasterxml.jackson.annotation.JsonAutoDetect; +import com.fasterxml.jackson.annotation.PropertyAccessor; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.type.TypeReference; +import com.fasterxml.jackson.databind.DeserializationContext; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.deser.std.FromStringDeserializer; +import com.fasterxml.jackson.databind.module.SimpleModule; +import com.fasterxml.jackson.databind.ser.std.ToStringSerializer; +import com.fasterxml.jackson.databind.type.TypeFactory; +import org.apache.logging.log4j.Level; + +import java.io.IOException; +import java.util.HashMap; +import java.util.List; + +/** + * Converts a Java object instance to JSON and vice versa + */ +public class JsonUtil { + private static class LevelDeserializer extends FromStringDeserializer { + + protected LevelDeserializer(Class vc) { + super(vc); + } + + @Override + protected Level _deserialize(String value, DeserializationContext ctxt) throws IOException { + return getLoggingLevel(value); + } + + /** + * Gets the logging level that matches loggingLevelString + *

+ * Returns null if there are no matches + * + * @param loggingLevelString + * @return + */ + private Level getLoggingLevel(String loggingLevelString) { + for (Level level : Level.values()) { + if (level.toString().equals(loggingLevelString)) { + return level; + } + } + return null; + } + + @Override + public Class handledType() { + return Level.class; + } + } + + private static ObjectMapper objectMapper = new ObjectMapper().findAndRegisterModules() + .configure(SerializationFeature.WRITE_DATES_AS_TIMESTAMPS, false) + .setVisibility(PropertyAccessor.ALL, JsonAutoDetect.Visibility.NONE) + .setVisibility(PropertyAccessor.FIELD, JsonAutoDetect.Visibility.ANY) + .registerModule(new SimpleModule("SimpleModule") + .addSerializer(Level.class, new ToStringSerializer()) + .addDeserializer(Level.class, new LevelDeserializer(Level.class))); + + /** + * Converts a given string representation of a JSON data to instance of a class + * @param The generic type to create an instance of + * @return The instance of T with the specified values in the JSON string + */ + public static T fromJsonString(String json, Class instanceClass) throws IOException { + return objectMapper.readValue(json, instanceClass); + } + + /** + * Converts a given instance of a class into its JSON data string representation + * @param instance The T object to be converted into the JSON string + * @param The generic type to create an instance of + * @return JSON data representation of the given class instance, in string + */ + public static String toJsonString(T instance) throws JsonProcessingException { + return objectMapper.writerWithDefaultPrettyPrinter().writeValueAsString(instance); + } + + public static List fromJsonStringToList(String json, Class referenceClass) throws IOException { + TypeFactory typeFactory = objectMapper.getTypeFactory(); + return objectMapper.readValue(json, typeFactory.constructCollectionType(List.class, referenceClass)); + } + + public static HashMap fromJsonStringToHashMap(String json, Class keyClass, Class valueClass) + throws IOException { + TypeFactory typeFactory = objectMapper.getTypeFactory(); + return objectMapper.readValue(json, typeFactory.constructMapType(HashMap.class, keyClass, valueClass)); + } + + public static T fromJsonStringToGivenType(String json, TypeReference typeReference) throws IOException { + return objectMapper.readValue(json, typeReference); + } +} diff --git a/src/main/java/commons/LibraryDescriptor.java b/src/main/java/commons/LibraryDescriptor.java new file mode 100644 index 000000000000..bc00f11ebe99 --- /dev/null +++ b/src/main/java/commons/LibraryDescriptor.java @@ -0,0 +1,47 @@ +package commons; + +import java.net.MalformedURLException; +import java.net.URL; + +/** + * Contains information for a dependency library, including its download link + */ +public class LibraryDescriptor { + + private String filename; + private URL downloadLink; + private commons.OsDetector.Os os; + + public LibraryDescriptor() {} // required for serialization + + public LibraryDescriptor(String filename, String downloadLink, commons.OsDetector.Os os) + throws MalformedURLException { + this.filename = filename; + this.downloadLink = new URL(downloadLink); + this.os = os; + } + + public String getFileName() { + return filename; + } + + public void setFilename(String filename) { + this.filename = filename; + } + + public URL getDownloadLink() { + return downloadLink; + } + + public void setDownloadLink(String downloadLink) throws MalformedURLException { + this.downloadLink = new URL(downloadLink); + } + + public commons.OsDetector.Os getOs() { + return os; + } + + public void setOs(commons.OsDetector.Os os) { + this.os = os; + } +} diff --git a/src/main/java/commons/OsDetector.java b/src/main/java/commons/OsDetector.java new file mode 100644 index 000000000000..b6ca8dfce8e6 --- /dev/null +++ b/src/main/java/commons/OsDetector.java @@ -0,0 +1,111 @@ +package commons; + +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStreamReader; + +/** + * Detect what Operating System (OS) the application is running in + */ +public class OsDetector { + private static final String osName = System.getProperty("os.name"); + + public enum Os { + WINDOWS, MAC, LINUX32, LINUX64, ANY, UNKNOWN + } + + public enum Architecture { + UNKNOWN, X86_64, I386, I686 + } + + public static boolean isOnWindows() { + return osName.startsWith("Windows"); + } + + public static boolean isOnMac() { + return osName.startsWith("Mac OS"); + } + + public static boolean isOn64BitsLinux() { + if (!isOnLinux()) { + return false; + } + + return getLinuxKernelArchitecture() == Architecture.X86_64; + } + + public static boolean isOn32BitsLinux() { + if (!isOnLinux()) { + return false; + } + + Architecture architecture = getLinuxKernelArchitecture(); + + return architecture == Architecture.I386 || architecture == Architecture.I686; + } + + public static boolean isOnLinux() { + return osName.startsWith("Linux"); + } + + public static Os getOs() { + if (isOnWindows()) { + return Os.WINDOWS; + } else if (isOnMac()) { + return Os.MAC; + } else if (isOn32BitsLinux()) { + return Os.LINUX32; + } else if (isOn64BitsLinux()) { + return Os.LINUX64; + } else { + return Os.UNKNOWN; + } + } + + private static Architecture unknownArchitecture(Exception e) { + return Architecture.UNKNOWN; + } + + private static Architecture getLinuxKernelArchitecture() { + try { + Process process = Runtime.getRuntime().exec("uname -m"); + process.waitFor(); + + try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream(), "UTF-8"))) { + StringBuilder output = new StringBuilder(); + String nextOutputLine = reader.readLine(); + + while (nextOutputLine != null) { + output.append(nextOutputLine).append(' '); + nextOutputLine = reader.readLine(); + } + + return getArchitectureFromString(output.toString()); + } + + } catch (IOException | InterruptedException e) { + return unknownArchitecture(e); + } + } + + /** + * Finds a relevant sub-string that characterizes an os architecture and + * return the corresponding Architecture enum + * + * @param architectureDescription a string description of an os architecture + * @return the corresponding Architecture enum + */ + public static Architecture getArchitectureFromString(String architectureDescription) { + if (architectureDescription == null) { + return Architecture.UNKNOWN; + } else if (architectureDescription.contains("x86_64")) { + return Architecture.X86_64; + } else if (architectureDescription.contains("i386")) { + return Architecture.I386; + } else if (architectureDescription.contains("i686")) { + return Architecture.I686; + } else { + return Architecture.UNKNOWN; + } + } +} diff --git a/src/main/java/commons/PlatformExecUtil.java b/src/main/java/commons/PlatformExecUtil.java new file mode 100644 index 000000000000..1fcb8bd310ec --- /dev/null +++ b/src/main/java/commons/PlatformExecUtil.java @@ -0,0 +1,107 @@ +package commons; + +import javafx.application.Platform; + +import java.util.concurrent.*; + +/** + * Contains utility methods for running code in various ways, + * on or off (but close to) the JavaFX application thread. + */ +public final class PlatformExecUtil { + + private static final ScheduledExecutorService SCHEDULER = Executors.newSingleThreadScheduledExecutor(); + + public static void runLater(Runnable action) { + Platform.runLater(action); + } + + /** + * If called from FX thread, will run immediately and return completed future. + * If called outside FX thread, returns immediately, callback is queued and run asynchronously on FX thread. + */ + public static Future call(Callable callback) { + final FutureTask task = new FutureTask<>(callback); + if (isFxThread()) { + task.run(); + } else { + runLater(task); + } + return task; + } + + /** + * Runs callback on FX thread and wait till the result is returned. Returns custom value if there was any thread + * execution exceptions thrown. + * @see #call(Callable) + * @return {@code failValue} if there was an exception during execution, else result of {@code callback} + */ + public static T callAndWait(Callable callback, T failValue) { + try { + return call(callback).get(); + } catch (InterruptedException | ExecutionException e) { + e.printStackTrace(); + return failValue; // execution exception, unable to retrieve data + } + } + + public static void runLaterDelayed(Runnable action, long delay, TimeUnit unit) { + SCHEDULER.schedule(() -> Platform.runLater(action), delay, unit); + } + + public static Future callLaterDelayed(Callable action, long delay, TimeUnit unit) { + final FutureTask task = new FutureTask(action); + SCHEDULER.schedule(() -> Platform.runLater(task), delay, unit); + return task; + } + + /** + * Blocks until the JavaFX event queue becomes empty. + */ + public static void waitOnFxThread() { + runLaterAndWait(() -> {}); + } + + /** + * Runs an action on the JavaFX Application Thread and blocks until it completes. + * Similar to {@link #runAndWait(Runnable) runAndWait}, but always enqueues the + * action, eschewing checking the current thread. + * + * @param action The action to run on the JavaFX Application Thread + */ + public static void runLaterAndWait(Runnable action) { + assert action != null : "Non-null action required"; + CountDownLatch latch = new CountDownLatch(1); + Platform.runLater(() -> { + action.run(); + latch.countDown(); + }); + try { + latch.await(); + } catch (InterruptedException ignored) { + } + } + + /** + * Synchronous version of Platform.runLater, like SwingUtilities.invokeAndWait. + * Caveat: will execute immediately when invoked from the JavaFX application thread + * instead of being queued up for execution. + * + * @param action The action to execute on the JavaFX Application Thread. + */ + public static void runAndWait(Runnable action) { + assert action != null : "Non-null action required"; + if (isFxThread()) { + action.run(); + return; + } + runLaterAndWait(action); + } + + public static boolean isFxThread() { + return Platform.isFxApplicationThread(); + } + + private PlatformExecUtil() { + } +} diff --git a/src/main/java/commons/StringUtil.java b/src/main/java/commons/StringUtil.java new file mode 100644 index 000000000000..a9f866ed0007 --- /dev/null +++ b/src/main/java/commons/StringUtil.java @@ -0,0 +1,10 @@ +package commons; + +/** + * Helper functions for handling strings. + */ +public class StringUtil { + public static boolean containsIgnoreCase(String source, String query) { + return source.toLowerCase().contains(query.toLowerCase()); + } +} diff --git a/src/main/java/commons/UrlUtil.java b/src/main/java/commons/UrlUtil.java new file mode 100644 index 000000000000..848a1af7c1fd --- /dev/null +++ b/src/main/java/commons/UrlUtil.java @@ -0,0 +1,21 @@ +package commons; + +import java.net.URL; + +/** + * An utility class for URL + */ +public class UrlUtil { + + public static boolean compareBaseUrls(URL url1, URL url2) { + + if (url1 == null || url2 == null) { + return false; + } + return url1.getHost().toLowerCase().replaceFirst("www.", "") + .equals(url2.getHost().replaceFirst("www.", "").toLowerCase()) + && url1.getPath().replaceAll("/", "").toLowerCase() + .equals(url2.getPath().replaceAll("/", "").toLowerCase()); + } + +} diff --git a/src/main/java/commons/Version.java b/src/main/java/commons/Version.java new file mode 100644 index 000000000000..f1b04673c0a7 --- /dev/null +++ b/src/main/java/commons/Version.java @@ -0,0 +1,102 @@ +package commons; + +import com.fasterxml.jackson.annotation.JsonCreator; +import com.fasterxml.jackson.annotation.JsonValue; + +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * Represents a version with major, minor and patch number + */ +public class Version implements Comparable { + + public static final String VERSION_REGEX = "V(\\d+)\\.(\\d+)\\.(\\d+)(ea)?"; + + private static final String EXCEPTION_STRING_NOT_VERSION = "String is not a valid Version. %s"; + + private static final Pattern VERSION_PATTERN = Pattern.compile(VERSION_REGEX); + + private final int major; + private final int minor; + private final int patch; + private final boolean isEarlyAccess; + + public Version(int major, int minor, int patch, boolean isEarlyAccess) { + this.major = major; + this.minor = minor; + this.patch = patch; + this.isEarlyAccess = isEarlyAccess; + } + + public int getMajor() { + return major; + } + + public int getMinor() { + return minor; + } + + public int getPatch() { + return patch; + } + + public boolean isEarlyAccess() { + return isEarlyAccess; + } + + /** + * Parses a version number string in the format V1.2.3. + * @param versionString version number string + * @return a Version object + */ + @JsonCreator + public static Version fromString(String versionString) throws IllegalArgumentException { + Matcher versionMatcher = VERSION_PATTERN.matcher(versionString); + + if (!versionMatcher.find()) { + throw new IllegalArgumentException(String.format(EXCEPTION_STRING_NOT_VERSION, versionString)); + } + + return new Version(Integer.parseInt(versionMatcher.group(1)), + Integer.parseInt(versionMatcher.group(2)), + Integer.parseInt(versionMatcher.group(3)), + versionMatcher.group(4) == null ? false : true); + } + + @JsonValue + public String toString() { + return String.format("V%d.%d.%d%s", major, minor, patch, isEarlyAccess ? "ea" : ""); + } + + @Override + public int compareTo(Version other) { + return this.major != other.major ? this.major - other.major : + this.minor != other.minor ? this.minor - other.minor : + this.patch != other.patch ? this.patch - other.patch : + this.isEarlyAccess == other.isEarlyAccess() ? 0 : + this.isEarlyAccess ? -1 : 1; + } + + @Override + public boolean equals(Object obj) { + if (obj == null) { + return false; + } + if (!(obj instanceof Version)) { + return false; + } + final Version other = (Version) obj; + + return this.compareTo(other) == 0; + } + + @Override + public int hashCode() { + String hash = String.format("%03d%03d%03d", major, minor, patch); + if (!isEarlyAccess) { + hash = "1" + hash; + } + return Integer.parseInt(hash); + } +} diff --git a/src/main/java/commons/VersionData.java b/src/main/java/commons/VersionData.java new file mode 100644 index 000000000000..54fbe512cf02 --- /dev/null +++ b/src/main/java/commons/VersionData.java @@ -0,0 +1,35 @@ +package commons; + +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.annotation.JsonPropertyOrder; + +import java.util.ArrayList; +import java.util.List; + +/** + * Lists latest application's version, main app download link and libraries descriptors + */ +@JsonPropertyOrder({ "version", "libraries" }) +public class VersionData { + @JsonProperty("version") + private String versionString; + private List libraries = new ArrayList<>(); + + public VersionData() {} // required for serialization + + public String getVersion() { + return versionString; + } + + public void setVersion(String versionString) { + this.versionString = versionString; + } + + public void setLibraries(List libraries) { + this.libraries = libraries; + } + + public List getLibraries() { + return libraries; + } +} diff --git a/src/main/java/commons/XmlUtil.java b/src/main/java/commons/XmlUtil.java new file mode 100644 index 000000000000..e2746df4e318 --- /dev/null +++ b/src/main/java/commons/XmlUtil.java @@ -0,0 +1,107 @@ +package commons; + +import javax.xml.bind.JAXBContext; +import javax.xml.bind.JAXBException; +import javax.xml.bind.Marshaller; +import javax.xml.bind.Unmarshaller; +import javax.xml.bind.annotation.adapters.XmlAdapter; +import java.io.File; +import java.io.FileNotFoundException; +import java.time.LocalDate; +import java.time.LocalDateTime; +import java.util.UUID; + +/** + * Helps with reading from and writing to XML files. + */ +public class XmlUtil { + + /** + * Returns the xml data in the file as an object of the specified type. + * + * @param file Points to a valid xml file containing data that match the {@code classToConvert}. + * Cannot be null. + * @param classToConvert The class corresponding to the xml data. + * Cannot be null. + * @throws FileNotFoundException Thrown if the file is missing. + * @throws JAXBException Thrown if the file is empty or does not have the correct format. + */ + @SuppressWarnings("unchecked") + public static T getDataFromFile(File file, Class classToConvert) + throws FileNotFoundException, JAXBException { + + assert file != null; + assert classToConvert != null; + + if (!FileUtil.isFileExists(file)) { + throw new FileNotFoundException("File not found : " + file.getAbsolutePath()); + } + + JAXBContext context = JAXBContext.newInstance(classToConvert); + Unmarshaller um = context.createUnmarshaller(); + + return ((T) um.unmarshal(file)); + } + + /** + * Saves the data in the file in xml format. + * + * @param file Points to a valid xml file containing data that match the {@code classToConvert}. + * Cannot be null. + * @throws FileNotFoundException Thrown if the file is missing. + * @throws JAXBException Thrown if there is an error during converting the data + * into xml and writing to the file. + */ + public static void saveDataToFile(File file, T data) throws FileNotFoundException, JAXBException { + + assert file != null; + assert data != null; + + if (!file.exists()) { + throw new FileNotFoundException("File not found : " + file.getAbsolutePath()); + } + + JAXBContext context = JAXBContext.newInstance(data.getClass()); + Marshaller m = context.createMarshaller(); + m.setProperty(Marshaller.JAXB_FORMATTED_OUTPUT, true); + + m.marshal(data, file); + } + + + public static class UuidAdapter extends XmlAdapter { + @Override + public UUID unmarshal(String v) { + return UUID.fromString(v); + } + + @Override + public String marshal(UUID v) { + return v.toString(); + } + } + + public static class LocalDateTimeAdapter extends XmlAdapter { + @Override + public LocalDateTime unmarshal(String v) throws Exception { + return LocalDateTime.parse(v); + } + + @Override + public String marshal(LocalDateTime v) throws Exception { + return v.toString(); + } + } + + public static class LocalDateAdapter extends XmlAdapter { + @Override + public LocalDate unmarshal(String v) { + return LocalDate.parse(v); + } + + @Override + public String marshal(LocalDate v) { + return v.toString(); + } + } +} diff --git a/src/main/java/launcher/Launcher.java b/src/main/java/launcher/Launcher.java new file mode 100644 index 000000000000..2d1fcb410539 --- /dev/null +++ b/src/main/java/launcher/Launcher.java @@ -0,0 +1,67 @@ +package launcher; + +import javafx.application.Application; +import javafx.application.Platform; +import javafx.scene.control.Alert; +import javafx.stage.Stage; + +import java.io.File; +import java.io.IOException; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; + +/** + * Launcher for the address book application + */ +public class Launcher extends Application { + private static final String CLASS_PATH = File.pathSeparator + "lib" + File.separator + "*"; + private static final String ERROR_LAUNCH = "Failed to launch"; + private static final String ERROR_RUNNING = "Failed to run application"; + private static final String ERROR_TRY_AGAIN = "Please try again, or contact developer if it keeps failing."; + + private final ExecutorService pool = Executors.newSingleThreadExecutor(); + + @Override + public void start(Stage primaryStage) throws Exception { + pool.execute(() -> { + try { + run(); + } catch (IOException e) { + showErrorDialogAndQuit(ERROR_LAUNCH, e.getMessage(), ERROR_TRY_AGAIN); + } + }); + } + + private void run() throws IOException { + runMainApplication(); + stop(); + } + + private void runMainApplication() throws IOException { + try { + String command = String.format("java -ea -cp %s address.MainApp", CLASS_PATH); + System.out.println("Starting main application: " + command); + Runtime.getRuntime().exec(command, null, new File(System.getProperty("user.dir"))); + System.out.println("Main application launched"); + } catch (IOException e) { + throw new IOException(ERROR_RUNNING, e); + } + } + + private void showErrorDialogAndQuit(String title, String headerText, String contentText) { + Platform.runLater(() -> { + final Alert alert = new Alert(Alert.AlertType.ERROR); + alert.setTitle(title); + alert.setHeaderText(headerText); + alert.setContentText(contentText); + alert.showAndWait(); + stop(); + }); + } + + @Override + public void stop() { + Platform.exit(); + System.exit(0); + } +} diff --git a/src/main/resources/help_html/css/bootstrap.min.css b/src/main/resources/help_html/css/bootstrap.min.css new file mode 100644 index 000000000000..679272d25859 --- /dev/null +++ b/src/main/resources/help_html/css/bootstrap.min.css @@ -0,0 +1,7 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ + +/*! normalize.css v3.0.0 | MIT License | git.io/normalize */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,hgroup,main,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block;vertical-align:baseline}audio:not([controls]){display:none;height:0}[hidden],template{display:none}a{background:0 0}a:active,a:hover{outline:0}abbr[title]{border-bottom:1px dotted}b,strong{font-weight:700}dfn{font-style:italic}h1{font-size:2em;margin:.67em 0}mark{background:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sup{top:-.5em}sub{bottom:-.25em}img{border:0}svg:not(:root){overflow:hidden}figure{margin:1em 40px}hr{-moz-box-sizing:content-box;box-sizing:content-box;height:0}pre{overflow:auto}code,kbd,pre,samp{font-family:monospace,monospace;font-size:1em}button,input,optgroup,select,textarea{color:inherit;font:inherit;margin:0}button{overflow:visible}button,select{text-transform:none}button,html input[type=button],input[type=reset],input[type=submit]{-webkit-appearance:button;cursor:pointer}button[disabled],html input[disabled]{cursor:default}button::-moz-focus-inner,input::-moz-focus-inner{border:0;padding:0}input{line-height:normal}input[type=checkbox],input[type=radio]{box-sizing:border-box;padding:0}input[type=number]::-webkit-inner-spin-button,input[type=number]::-webkit-outer-spin-button{height:auto}input[type=search]{-webkit-appearance:textfield;-moz-box-sizing:content-box;-webkit-box-sizing:content-box;box-sizing:content-box}input[type=search]::-webkit-search-cancel-button,input[type=search]::-webkit-search-decoration{-webkit-appearance:none}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{border:0;padding:0}textarea{overflow:auto}optgroup{font-weight:700}table{border-collapse:collapse;border-spacing:0}td,th{padding:0}@media print{*{text-shadow:none!important;color:#000!important;background:transparent!important;box-shadow:none!important}a,a:visited{text-decoration:underline}a[href]:after{content:" (" attr(href) ")"}abbr[title]:after{content:" (" attr(title) ")"}a[href^="javascript:"]:after,a[href^="#"]:after{content:""}pre,blockquote{border:1px solid #999;page-break-inside:avoid}thead{display:table-header-group}tr,img{page-break-inside:avoid}img{max-width:100%!important}p,h2,h3{orphans:3;widows:3}h2,h3{page-break-after:avoid}select{background:#fff!important}.navbar{display:none}.table td,.table th{background-color:#fff!important}.btn>.caret,.dropup>.btn>.caret{border-top-color:#000!important}.label{border:1px solid #000}.table{border-collapse:collapse!important}.table-bordered th,.table-bordered td{border:1px solid #ddd!important}}*{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}:before,:after{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}html{font-size:62.5%;-webkit-tap-highlight-color:rgba(0,0,0,0)}body{font-family:"Helvetica Neue",Helvetica,Arial,sans-serif;font-size:14px;line-height:1.42857143;color:#333;background-color:#fff}input,button,select,textarea{font-family:inherit;font-size:inherit;line-height:inherit}a{color:#428bca;text-decoration:none}a:hover,a:focus{color:#2a6496;text-decoration:underline}a:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}figure{margin:0}img{vertical-align:middle}.img-responsive,.thumbnail>img,.thumbnail a>img,.carousel-inner>.item>img,.carousel-inner>.item>a>img{display:block;max-width:100%;height:auto}.img-rounded{border-radius:6px}.img-thumbnail{padding:4px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out;display:inline-block;max-width:100%;height:auto}.img-circle{border-radius:50%}hr{margin-top:20px;margin-bottom:20px;border:0;border-top:1px solid #eee}.sr-only{position:absolute;width:1px;height:1px;margin:-1px;padding:0;overflow:hidden;clip:rect(0,0,0,0);border:0}h1,h2,h3,h4,h5,h6,.h1,.h2,.h3,.h4,.h5,.h6{font-family:inherit;font-weight:500;line-height:1.1;color:inherit}h1 small,h2 small,h3 small,h4 small,h5 small,h6 small,.h1 small,.h2 small,.h3 small,.h4 small,.h5 small,.h6 small,h1 .small,h2 .small,h3 .small,h4 .small,h5 .small,h6 .small,.h1 .small,.h2 .small,.h3 .small,.h4 .small,.h5 .small,.h6 .small{font-weight:400;line-height:1;color:#999}h1,.h1,h2,.h2,h3,.h3{margin-top:20px;margin-bottom:10px}h1 small,.h1 small,h2 small,.h2 small,h3 small,.h3 small,h1 .small,.h1 .small,h2 .small,.h2 .small,h3 .small,.h3 .small{font-size:65%}h4,.h4,h5,.h5,h6,.h6{margin-top:10px;margin-bottom:10px}h4 small,.h4 small,h5 small,.h5 small,h6 small,.h6 small,h4 .small,.h4 .small,h5 .small,.h5 .small,h6 .small,.h6 .small{font-size:75%}h1,.h1{font-size:36px}h2,.h2{font-size:30px}h3,.h3{font-size:24px}h4,.h4{font-size:18px}h5,.h5{font-size:14px}h6,.h6{font-size:12px}p{margin:0 0 10px}.lead{margin-bottom:20px;font-size:16px;font-weight:200;line-height:1.4}@media (min-width:768px){.lead{font-size:21px}}small,.small{font-size:85%}cite{font-style:normal}.text-left{text-align:left}.text-right{text-align:right}.text-center{text-align:center}.text-justify{text-align:justify}.text-muted{color:#999}.text-primary{color:#428bca}a.text-primary:hover{color:#3071a9}.text-success{color:#3c763d}a.text-success:hover{color:#2b542c}.text-info{color:#31708f}a.text-info:hover{color:#245269}.text-warning{color:#8a6d3b}a.text-warning:hover{color:#66512c}.text-danger{color:#a94442}a.text-danger:hover{color:#843534}.bg-primary{color:#fff;background-color:#428bca}a.bg-primary:hover{background-color:#3071a9}.bg-success{background-color:#dff0d8}a.bg-success:hover{background-color:#c1e2b3}.bg-info{background-color:#d9edf7}a.bg-info:hover{background-color:#afd9ee}.bg-warning{background-color:#fcf8e3}a.bg-warning:hover{background-color:#f7ecb5}.bg-danger{background-color:#f2dede}a.bg-danger:hover{background-color:#e4b9b9}.page-header{padding-bottom:9px;margin:40px 0 20px;border-bottom:1px solid #eee}ul,ol{margin-top:0;margin-bottom:10px}ul ul,ol ul,ul ol,ol ol{margin-bottom:0}.list-unstyled{padding-left:0;list-style:none}.list-inline{padding-left:0;list-style:none;margin-left:-5px}.list-inline>li{display:inline-block;padding-left:5px;padding-right:5px}dl{margin-top:0;margin-bottom:20px}dt,dd{line-height:1.42857143}dt{font-weight:700}dd{margin-left:0}@media (min-width:768px){.dl-horizontal dt{float:left;width:160px;clear:left;text-align:right;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dl-horizontal dd{margin-left:180px}}abbr[title],abbr[data-original-title]{cursor:help;border-bottom:1px dotted #999}.initialism{font-size:90%;text-transform:uppercase}blockquote{padding:10px 20px;margin:0 0 20px;font-size:17.5px;border-left:5px solid #eee}blockquote p:last-child,blockquote ul:last-child,blockquote ol:last-child{margin-bottom:0}blockquote footer,blockquote small,blockquote .small{display:block;font-size:80%;line-height:1.42857143;color:#999}blockquote footer:before,blockquote small:before,blockquote .small:before{content:'\2014 \00A0'}.blockquote-reverse,blockquote.pull-right{padding-right:15px;padding-left:0;border-right:5px solid #eee;border-left:0;text-align:right}.blockquote-reverse footer:before,blockquote.pull-right footer:before,.blockquote-reverse small:before,blockquote.pull-right small:before,.blockquote-reverse .small:before,blockquote.pull-right .small:before{content:''}.blockquote-reverse footer:after,blockquote.pull-right footer:after,.blockquote-reverse small:after,blockquote.pull-right small:after,.blockquote-reverse .small:after,blockquote.pull-right .small:after{content:'\00A0 \2014'}blockquote:before,blockquote:after{content:""}address{margin-bottom:20px;font-style:normal;line-height:1.42857143}code,kbd,pre,samp{font-family:Menlo,Monaco,Consolas,"Courier New",monospace}code{padding:2px 4px;font-size:90%;color:#c7254e;background-color:#f9f2f4;white-space:nowrap;border-radius:4px}kbd{padding:2px 4px;font-size:90%;color:#fff;background-color:#333;border-radius:3px;box-shadow:inset 0 -1px 0 rgba(0,0,0,.25)}pre{display:block;padding:9.5px;margin:0 0 10px;font-size:13px;line-height:1.42857143;word-break:break-all;word-wrap:break-word;color:#333;background-color:#f5f5f5;border:1px solid #ccc;border-radius:4px}pre code{padding:0;font-size:inherit;color:inherit;white-space:pre-wrap;background-color:transparent;border-radius:0}.pre-scrollable{max-height:340px;overflow-y:scroll}.container{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}@media (min-width:768px){.container{width:750px}}@media (min-width:992px){.container{width:970px}}@media (min-width:1200px){.container{width:1170px}}.container-fluid{margin-right:auto;margin-left:auto;padding-left:15px;padding-right:15px}.row{margin-left:-15px;margin-right:-15px}.col-xs-1,.col-sm-1,.col-md-1,.col-lg-1,.col-xs-2,.col-sm-2,.col-md-2,.col-lg-2,.col-xs-3,.col-sm-3,.col-md-3,.col-lg-3,.col-xs-4,.col-sm-4,.col-md-4,.col-lg-4,.col-xs-5,.col-sm-5,.col-md-5,.col-lg-5,.col-xs-6,.col-sm-6,.col-md-6,.col-lg-6,.col-xs-7,.col-sm-7,.col-md-7,.col-lg-7,.col-xs-8,.col-sm-8,.col-md-8,.col-lg-8,.col-xs-9,.col-sm-9,.col-md-9,.col-lg-9,.col-xs-10,.col-sm-10,.col-md-10,.col-lg-10,.col-xs-11,.col-sm-11,.col-md-11,.col-lg-11,.col-xs-12,.col-sm-12,.col-md-12,.col-lg-12{position:relative;min-height:1px;padding-left:15px;padding-right:15px}.col-xs-1,.col-xs-2,.col-xs-3,.col-xs-4,.col-xs-5,.col-xs-6,.col-xs-7,.col-xs-8,.col-xs-9,.col-xs-10,.col-xs-11,.col-xs-12{float:left}.col-xs-12{width:100%}.col-xs-11{width:91.66666667%}.col-xs-10{width:83.33333333%}.col-xs-9{width:75%}.col-xs-8{width:66.66666667%}.col-xs-7{width:58.33333333%}.col-xs-6{width:50%}.col-xs-5{width:41.66666667%}.col-xs-4{width:33.33333333%}.col-xs-3{width:25%}.col-xs-2{width:16.66666667%}.col-xs-1{width:8.33333333%}.col-xs-pull-12{right:100%}.col-xs-pull-11{right:91.66666667%}.col-xs-pull-10{right:83.33333333%}.col-xs-pull-9{right:75%}.col-xs-pull-8{right:66.66666667%}.col-xs-pull-7{right:58.33333333%}.col-xs-pull-6{right:50%}.col-xs-pull-5{right:41.66666667%}.col-xs-pull-4{right:33.33333333%}.col-xs-pull-3{right:25%}.col-xs-pull-2{right:16.66666667%}.col-xs-pull-1{right:8.33333333%}.col-xs-pull-0{right:0}.col-xs-push-12{left:100%}.col-xs-push-11{left:91.66666667%}.col-xs-push-10{left:83.33333333%}.col-xs-push-9{left:75%}.col-xs-push-8{left:66.66666667%}.col-xs-push-7{left:58.33333333%}.col-xs-push-6{left:50%}.col-xs-push-5{left:41.66666667%}.col-xs-push-4{left:33.33333333%}.col-xs-push-3{left:25%}.col-xs-push-2{left:16.66666667%}.col-xs-push-1{left:8.33333333%}.col-xs-push-0{left:0}.col-xs-offset-12{margin-left:100%}.col-xs-offset-11{margin-left:91.66666667%}.col-xs-offset-10{margin-left:83.33333333%}.col-xs-offset-9{margin-left:75%}.col-xs-offset-8{margin-left:66.66666667%}.col-xs-offset-7{margin-left:58.33333333%}.col-xs-offset-6{margin-left:50%}.col-xs-offset-5{margin-left:41.66666667%}.col-xs-offset-4{margin-left:33.33333333%}.col-xs-offset-3{margin-left:25%}.col-xs-offset-2{margin-left:16.66666667%}.col-xs-offset-1{margin-left:8.33333333%}.col-xs-offset-0{margin-left:0}@media (min-width:768px){.col-sm-1,.col-sm-2,.col-sm-3,.col-sm-4,.col-sm-5,.col-sm-6,.col-sm-7,.col-sm-8,.col-sm-9,.col-sm-10,.col-sm-11,.col-sm-12{float:left}.col-sm-12{width:100%}.col-sm-11{width:91.66666667%}.col-sm-10{width:83.33333333%}.col-sm-9{width:75%}.col-sm-8{width:66.66666667%}.col-sm-7{width:58.33333333%}.col-sm-6{width:50%}.col-sm-5{width:41.66666667%}.col-sm-4{width:33.33333333%}.col-sm-3{width:25%}.col-sm-2{width:16.66666667%}.col-sm-1{width:8.33333333%}.col-sm-pull-12{right:100%}.col-sm-pull-11{right:91.66666667%}.col-sm-pull-10{right:83.33333333%}.col-sm-pull-9{right:75%}.col-sm-pull-8{right:66.66666667%}.col-sm-pull-7{right:58.33333333%}.col-sm-pull-6{right:50%}.col-sm-pull-5{right:41.66666667%}.col-sm-pull-4{right:33.33333333%}.col-sm-pull-3{right:25%}.col-sm-pull-2{right:16.66666667%}.col-sm-pull-1{right:8.33333333%}.col-sm-pull-0{right:0}.col-sm-push-12{left:100%}.col-sm-push-11{left:91.66666667%}.col-sm-push-10{left:83.33333333%}.col-sm-push-9{left:75%}.col-sm-push-8{left:66.66666667%}.col-sm-push-7{left:58.33333333%}.col-sm-push-6{left:50%}.col-sm-push-5{left:41.66666667%}.col-sm-push-4{left:33.33333333%}.col-sm-push-3{left:25%}.col-sm-push-2{left:16.66666667%}.col-sm-push-1{left:8.33333333%}.col-sm-push-0{left:0}.col-sm-offset-12{margin-left:100%}.col-sm-offset-11{margin-left:91.66666667%}.col-sm-offset-10{margin-left:83.33333333%}.col-sm-offset-9{margin-left:75%}.col-sm-offset-8{margin-left:66.66666667%}.col-sm-offset-7{margin-left:58.33333333%}.col-sm-offset-6{margin-left:50%}.col-sm-offset-5{margin-left:41.66666667%}.col-sm-offset-4{margin-left:33.33333333%}.col-sm-offset-3{margin-left:25%}.col-sm-offset-2{margin-left:16.66666667%}.col-sm-offset-1{margin-left:8.33333333%}.col-sm-offset-0{margin-left:0}}@media (min-width:992px){.col-md-1,.col-md-2,.col-md-3,.col-md-4,.col-md-5,.col-md-6,.col-md-7,.col-md-8,.col-md-9,.col-md-10,.col-md-11,.col-md-12{float:left}.col-md-12{width:100%}.col-md-11{width:91.66666667%}.col-md-10{width:83.33333333%}.col-md-9{width:75%}.col-md-8{width:66.66666667%}.col-md-7{width:58.33333333%}.col-md-6{width:50%}.col-md-5{width:41.66666667%}.col-md-4{width:33.33333333%}.col-md-3{width:25%}.col-md-2{width:16.66666667%}.col-md-1{width:8.33333333%}.col-md-pull-12{right:100%}.col-md-pull-11{right:91.66666667%}.col-md-pull-10{right:83.33333333%}.col-md-pull-9{right:75%}.col-md-pull-8{right:66.66666667%}.col-md-pull-7{right:58.33333333%}.col-md-pull-6{right:50%}.col-md-pull-5{right:41.66666667%}.col-md-pull-4{right:33.33333333%}.col-md-pull-3{right:25%}.col-md-pull-2{right:16.66666667%}.col-md-pull-1{right:8.33333333%}.col-md-pull-0{right:0}.col-md-push-12{left:100%}.col-md-push-11{left:91.66666667%}.col-md-push-10{left:83.33333333%}.col-md-push-9{left:75%}.col-md-push-8{left:66.66666667%}.col-md-push-7{left:58.33333333%}.col-md-push-6{left:50%}.col-md-push-5{left:41.66666667%}.col-md-push-4{left:33.33333333%}.col-md-push-3{left:25%}.col-md-push-2{left:16.66666667%}.col-md-push-1{left:8.33333333%}.col-md-push-0{left:0}.col-md-offset-12{margin-left:100%}.col-md-offset-11{margin-left:91.66666667%}.col-md-offset-10{margin-left:83.33333333%}.col-md-offset-9{margin-left:75%}.col-md-offset-8{margin-left:66.66666667%}.col-md-offset-7{margin-left:58.33333333%}.col-md-offset-6{margin-left:50%}.col-md-offset-5{margin-left:41.66666667%}.col-md-offset-4{margin-left:33.33333333%}.col-md-offset-3{margin-left:25%}.col-md-offset-2{margin-left:16.66666667%}.col-md-offset-1{margin-left:8.33333333%}.col-md-offset-0{margin-left:0}}@media (min-width:1200px){.col-lg-1,.col-lg-2,.col-lg-3,.col-lg-4,.col-lg-5,.col-lg-6,.col-lg-7,.col-lg-8,.col-lg-9,.col-lg-10,.col-lg-11,.col-lg-12{float:left}.col-lg-12{width:100%}.col-lg-11{width:91.66666667%}.col-lg-10{width:83.33333333%}.col-lg-9{width:75%}.col-lg-8{width:66.66666667%}.col-lg-7{width:58.33333333%}.col-lg-6{width:50%}.col-lg-5{width:41.66666667%}.col-lg-4{width:33.33333333%}.col-lg-3{width:25%}.col-lg-2{width:16.66666667%}.col-lg-1{width:8.33333333%}.col-lg-pull-12{right:100%}.col-lg-pull-11{right:91.66666667%}.col-lg-pull-10{right:83.33333333%}.col-lg-pull-9{right:75%}.col-lg-pull-8{right:66.66666667%}.col-lg-pull-7{right:58.33333333%}.col-lg-pull-6{right:50%}.col-lg-pull-5{right:41.66666667%}.col-lg-pull-4{right:33.33333333%}.col-lg-pull-3{right:25%}.col-lg-pull-2{right:16.66666667%}.col-lg-pull-1{right:8.33333333%}.col-lg-pull-0{right:0}.col-lg-push-12{left:100%}.col-lg-push-11{left:91.66666667%}.col-lg-push-10{left:83.33333333%}.col-lg-push-9{left:75%}.col-lg-push-8{left:66.66666667%}.col-lg-push-7{left:58.33333333%}.col-lg-push-6{left:50%}.col-lg-push-5{left:41.66666667%}.col-lg-push-4{left:33.33333333%}.col-lg-push-3{left:25%}.col-lg-push-2{left:16.66666667%}.col-lg-push-1{left:8.33333333%}.col-lg-push-0{left:0}.col-lg-offset-12{margin-left:100%}.col-lg-offset-11{margin-left:91.66666667%}.col-lg-offset-10{margin-left:83.33333333%}.col-lg-offset-9{margin-left:75%}.col-lg-offset-8{margin-left:66.66666667%}.col-lg-offset-7{margin-left:58.33333333%}.col-lg-offset-6{margin-left:50%}.col-lg-offset-5{margin-left:41.66666667%}.col-lg-offset-4{margin-left:33.33333333%}.col-lg-offset-3{margin-left:25%}.col-lg-offset-2{margin-left:16.66666667%}.col-lg-offset-1{margin-left:8.33333333%}.col-lg-offset-0{margin-left:0}}table{max-width:100%;background-color:transparent}th{text-align:left}.table{width:100%;margin-bottom:20px}.table>thead>tr>th,.table>tbody>tr>th,.table>tfoot>tr>th,.table>thead>tr>td,.table>tbody>tr>td,.table>tfoot>tr>td{padding:8px;line-height:1.42857143;vertical-align:top;border-top:1px solid #ddd}.table>thead>tr>th{vertical-align:bottom;border-bottom:2px solid #ddd}.table>caption+thead>tr:first-child>th,.table>colgroup+thead>tr:first-child>th,.table>thead:first-child>tr:first-child>th,.table>caption+thead>tr:first-child>td,.table>colgroup+thead>tr:first-child>td,.table>thead:first-child>tr:first-child>td{border-top:0}.table>tbody+tbody{border-top:2px solid #ddd}.table .table{background-color:#fff}.table-condensed>thead>tr>th,.table-condensed>tbody>tr>th,.table-condensed>tfoot>tr>th,.table-condensed>thead>tr>td,.table-condensed>tbody>tr>td,.table-condensed>tfoot>tr>td{padding:5px}.table-bordered{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>tbody>tr>th,.table-bordered>tfoot>tr>th,.table-bordered>thead>tr>td,.table-bordered>tbody>tr>td,.table-bordered>tfoot>tr>td{border:1px solid #ddd}.table-bordered>thead>tr>th,.table-bordered>thead>tr>td{border-bottom-width:2px}.table-striped>tbody>tr:nth-child(odd)>td,.table-striped>tbody>tr:nth-child(odd)>th{background-color:#f9f9f9}.table-hover>tbody>tr:hover>td,.table-hover>tbody>tr:hover>th{background-color:#f5f5f5}table col[class*=col-]{position:static;float:none;display:table-column}table td[class*=col-],table th[class*=col-]{position:static;float:none;display:table-cell}.table>thead>tr>td.active,.table>tbody>tr>td.active,.table>tfoot>tr>td.active,.table>thead>tr>th.active,.table>tbody>tr>th.active,.table>tfoot>tr>th.active,.table>thead>tr.active>td,.table>tbody>tr.active>td,.table>tfoot>tr.active>td,.table>thead>tr.active>th,.table>tbody>tr.active>th,.table>tfoot>tr.active>th{background-color:#f5f5f5}.table-hover>tbody>tr>td.active:hover,.table-hover>tbody>tr>th.active:hover,.table-hover>tbody>tr.active:hover>td,.table-hover>tbody>tr.active:hover>th{background-color:#e8e8e8}.table>thead>tr>td.success,.table>tbody>tr>td.success,.table>tfoot>tr>td.success,.table>thead>tr>th.success,.table>tbody>tr>th.success,.table>tfoot>tr>th.success,.table>thead>tr.success>td,.table>tbody>tr.success>td,.table>tfoot>tr.success>td,.table>thead>tr.success>th,.table>tbody>tr.success>th,.table>tfoot>tr.success>th{background-color:#dff0d8}.table-hover>tbody>tr>td.success:hover,.table-hover>tbody>tr>th.success:hover,.table-hover>tbody>tr.success:hover>td,.table-hover>tbody>tr.success:hover>th{background-color:#d0e9c6}.table>thead>tr>td.info,.table>tbody>tr>td.info,.table>tfoot>tr>td.info,.table>thead>tr>th.info,.table>tbody>tr>th.info,.table>tfoot>tr>th.info,.table>thead>tr.info>td,.table>tbody>tr.info>td,.table>tfoot>tr.info>td,.table>thead>tr.info>th,.table>tbody>tr.info>th,.table>tfoot>tr.info>th{background-color:#d9edf7}.table-hover>tbody>tr>td.info:hover,.table-hover>tbody>tr>th.info:hover,.table-hover>tbody>tr.info:hover>td,.table-hover>tbody>tr.info:hover>th{background-color:#c4e3f3}.table>thead>tr>td.warning,.table>tbody>tr>td.warning,.table>tfoot>tr>td.warning,.table>thead>tr>th.warning,.table>tbody>tr>th.warning,.table>tfoot>tr>th.warning,.table>thead>tr.warning>td,.table>tbody>tr.warning>td,.table>tfoot>tr.warning>td,.table>thead>tr.warning>th,.table>tbody>tr.warning>th,.table>tfoot>tr.warning>th{background-color:#fcf8e3}.table-hover>tbody>tr>td.warning:hover,.table-hover>tbody>tr>th.warning:hover,.table-hover>tbody>tr.warning:hover>td,.table-hover>tbody>tr.warning:hover>th{background-color:#faf2cc}.table>thead>tr>td.danger,.table>tbody>tr>td.danger,.table>tfoot>tr>td.danger,.table>thead>tr>th.danger,.table>tbody>tr>th.danger,.table>tfoot>tr>th.danger,.table>thead>tr.danger>td,.table>tbody>tr.danger>td,.table>tfoot>tr.danger>td,.table>thead>tr.danger>th,.table>tbody>tr.danger>th,.table>tfoot>tr.danger>th{background-color:#f2dede}.table-hover>tbody>tr>td.danger:hover,.table-hover>tbody>tr>th.danger:hover,.table-hover>tbody>tr.danger:hover>td,.table-hover>tbody>tr.danger:hover>th{background-color:#ebcccc}@media (max-width:767px){.table-responsive{width:100%;margin-bottom:15px;overflow-y:hidden;overflow-x:scroll;-ms-overflow-style:-ms-autohiding-scrollbar;border:1px solid #ddd;-webkit-overflow-scrolling:touch}.table-responsive>.table{margin-bottom:0}.table-responsive>.table>thead>tr>th,.table-responsive>.table>tbody>tr>th,.table-responsive>.table>tfoot>tr>th,.table-responsive>.table>thead>tr>td,.table-responsive>.table>tbody>tr>td,.table-responsive>.table>tfoot>tr>td{white-space:nowrap}.table-responsive>.table-bordered{border:0}.table-responsive>.table-bordered>thead>tr>th:first-child,.table-responsive>.table-bordered>tbody>tr>th:first-child,.table-responsive>.table-bordered>tfoot>tr>th:first-child,.table-responsive>.table-bordered>thead>tr>td:first-child,.table-responsive>.table-bordered>tbody>tr>td:first-child,.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.table-responsive>.table-bordered>thead>tr>th:last-child,.table-responsive>.table-bordered>tbody>tr>th:last-child,.table-responsive>.table-bordered>tfoot>tr>th:last-child,.table-responsive>.table-bordered>thead>tr>td:last-child,.table-responsive>.table-bordered>tbody>tr>td:last-child,.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.table-responsive>.table-bordered>tbody>tr:last-child>th,.table-responsive>.table-bordered>tfoot>tr:last-child>th,.table-responsive>.table-bordered>tbody>tr:last-child>td,.table-responsive>.table-bordered>tfoot>tr:last-child>td{border-bottom:0}}fieldset{padding:0;margin:0;border:0;min-width:0}legend{display:block;width:100%;padding:0;margin-bottom:20px;font-size:21px;line-height:inherit;color:#333;border:0;border-bottom:1px solid #e5e5e5}label{display:inline-block;margin-bottom:5px;font-weight:700}input[type=search]{-webkit-box-sizing:border-box;-moz-box-sizing:border-box;box-sizing:border-box}input[type=radio],input[type=checkbox]{margin:4px 0 0;margin-top:1px \9;line-height:normal}input[type=file]{display:block}input[type=range]{display:block;width:100%}select[multiple],select[size]{height:auto}input[type=file]:focus,input[type=radio]:focus,input[type=checkbox]:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}output{display:block;padding-top:7px;font-size:14px;line-height:1.42857143;color:#555}.form-control{display:block;width:100%;height:34px;padding:6px 12px;font-size:14px;line-height:1.42857143;color:#555;background-color:#fff;background-image:none;border:1px solid #ccc;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075);-webkit-transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s;transition:border-color ease-in-out .15s,box-shadow ease-in-out .15s}.form-control:focus{border-color:#66afe9;outline:0;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6);box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 8px rgba(102,175,233,.6)}.form-control::-moz-placeholder{color:#999;opacity:1}.form-control:-ms-input-placeholder{color:#999}.form-control::-webkit-input-placeholder{color:#999}.form-control[disabled],.form-control[readonly],fieldset[disabled] .form-control{cursor:not-allowed;background-color:#eee;opacity:1}textarea.form-control{height:auto}input[type=search]{-webkit-appearance:none}input[type=date]{line-height:34px}.form-group{margin-bottom:15px}.radio,.checkbox{display:block;min-height:20px;margin-top:10px;margin-bottom:10px;padding-left:20px}.radio label,.checkbox label{display:inline;font-weight:400;cursor:pointer}.radio input[type=radio],.radio-inline input[type=radio],.checkbox input[type=checkbox],.checkbox-inline input[type=checkbox]{float:left;margin-left:-20px}.radio+.radio,.checkbox+.checkbox{margin-top:-5px}.radio-inline,.checkbox-inline{display:inline-block;padding-left:20px;margin-bottom:0;vertical-align:middle;font-weight:400;cursor:pointer}.radio-inline+.radio-inline,.checkbox-inline+.checkbox-inline{margin-top:0;margin-left:10px}input[type=radio][disabled],input[type=checkbox][disabled],.radio[disabled],.radio-inline[disabled],.checkbox[disabled],.checkbox-inline[disabled],fieldset[disabled] input[type=radio],fieldset[disabled] input[type=checkbox],fieldset[disabled] .radio,fieldset[disabled] .radio-inline,fieldset[disabled] .checkbox,fieldset[disabled] .checkbox-inline{cursor:not-allowed}.input-sm{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-sm{height:30px;line-height:30px}textarea.input-sm,select[multiple].input-sm{height:auto}.input-lg{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-lg{height:46px;line-height:46px}textarea.input-lg,select[multiple].input-lg{height:auto}.has-feedback{position:relative}.has-feedback .form-control{padding-right:42.5px}.has-feedback .form-control-feedback{position:absolute;top:25px;right:0;display:block;width:34px;height:34px;line-height:34px;text-align:center}.has-success .help-block,.has-success .control-label,.has-success .radio,.has-success .checkbox,.has-success .radio-inline,.has-success .checkbox-inline{color:#3c763d}.has-success .form-control{border-color:#3c763d;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-success .form-control:focus{border-color:#2b542c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #67b168}.has-success .input-group-addon{color:#3c763d;border-color:#3c763d;background-color:#dff0d8}.has-success .form-control-feedback{color:#3c763d}.has-warning .help-block,.has-warning .control-label,.has-warning .radio,.has-warning .checkbox,.has-warning .radio-inline,.has-warning .checkbox-inline{color:#8a6d3b}.has-warning .form-control{border-color:#8a6d3b;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-warning .form-control:focus{border-color:#66512c;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #c0a16b}.has-warning .input-group-addon{color:#8a6d3b;border-color:#8a6d3b;background-color:#fcf8e3}.has-warning .form-control-feedback{color:#8a6d3b}.has-error .help-block,.has-error .control-label,.has-error .radio,.has-error .checkbox,.has-error .radio-inline,.has-error .checkbox-inline{color:#a94442}.has-error .form-control{border-color:#a94442;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075);box-shadow:inset 0 1px 1px rgba(0,0,0,.075)}.has-error .form-control:focus{border-color:#843534;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483;box-shadow:inset 0 1px 1px rgba(0,0,0,.075),0 0 6px #ce8483}.has-error .input-group-addon{color:#a94442;border-color:#a94442;background-color:#f2dede}.has-error .form-control-feedback{color:#a94442}.form-control-static{margin-bottom:0}.help-block{display:block;margin-top:5px;margin-bottom:10px;color:#737373}@media (min-width:768px){.form-inline .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.form-inline .form-control{display:inline-block;width:auto;vertical-align:middle}.form-inline .input-group>.form-control{width:100%}.form-inline .control-label{margin-bottom:0;vertical-align:middle}.form-inline .radio,.form-inline .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.form-inline .radio input[type=radio],.form-inline .checkbox input[type=checkbox]{float:none;margin-left:0}.form-inline .has-feedback .form-control-feedback{top:0}}.form-horizontal .control-label,.form-horizontal .radio,.form-horizontal .checkbox,.form-horizontal .radio-inline,.form-horizontal .checkbox-inline{margin-top:0;margin-bottom:0;padding-top:7px}.form-horizontal .radio,.form-horizontal .checkbox{min-height:27px}.form-horizontal .form-group{margin-left:-15px;margin-right:-15px}.form-horizontal .form-control-static{padding-top:7px}@media (min-width:768px){.form-horizontal .control-label{text-align:right}}.form-horizontal .has-feedback .form-control-feedback{top:0;right:15px}.btn{display:inline-block;margin-bottom:0;font-weight:400;text-align:center;vertical-align:middle;cursor:pointer;background-image:none;border:1px solid transparent;white-space:nowrap;padding:6px 12px;font-size:14px;line-height:1.42857143;border-radius:4px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.btn:focus,.btn:active:focus,.btn.active:focus{outline:thin dotted;outline:5px auto -webkit-focus-ring-color;outline-offset:-2px}.btn:hover,.btn:focus{color:#333;text-decoration:none}.btn:active,.btn.active{outline:0;background-image:none;-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn.disabled,.btn[disabled],fieldset[disabled] .btn{cursor:not-allowed;pointer-events:none;opacity:.65;filter:alpha(opacity=65);-webkit-box-shadow:none;box-shadow:none}.btn-default{color:#333;background-color:#fff;border-color:#ccc}.btn-default:hover,.btn-default:focus,.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{color:#333;background-color:#ebebeb;border-color:#adadad}.btn-default:active,.btn-default.active,.open .dropdown-toggle.btn-default{background-image:none}.btn-default.disabled,.btn-default[disabled],fieldset[disabled] .btn-default,.btn-default.disabled:hover,.btn-default[disabled]:hover,fieldset[disabled] .btn-default:hover,.btn-default.disabled:focus,.btn-default[disabled]:focus,fieldset[disabled] .btn-default:focus,.btn-default.disabled:active,.btn-default[disabled]:active,fieldset[disabled] .btn-default:active,.btn-default.disabled.active,.btn-default[disabled].active,fieldset[disabled] .btn-default.active{background-color:#fff;border-color:#ccc}.btn-default .badge{color:#fff;background-color:#333}.btn-primary{color:#fff;background-color:#428bca;border-color:#357ebd}.btn-primary:hover,.btn-primary:focus,.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{color:#fff;background-color:#3276b1;border-color:#285e8e}.btn-primary:active,.btn-primary.active,.open .dropdown-toggle.btn-primary{background-image:none}.btn-primary.disabled,.btn-primary[disabled],fieldset[disabled] .btn-primary,.btn-primary.disabled:hover,.btn-primary[disabled]:hover,fieldset[disabled] .btn-primary:hover,.btn-primary.disabled:focus,.btn-primary[disabled]:focus,fieldset[disabled] .btn-primary:focus,.btn-primary.disabled:active,.btn-primary[disabled]:active,fieldset[disabled] .btn-primary:active,.btn-primary.disabled.active,.btn-primary[disabled].active,fieldset[disabled] .btn-primary.active{background-color:#428bca;border-color:#357ebd}.btn-primary .badge{color:#428bca;background-color:#fff}.btn-success{color:#fff;background-color:#5cb85c;border-color:#4cae4c}.btn-success:hover,.btn-success:focus,.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{color:#fff;background-color:#47a447;border-color:#398439}.btn-success:active,.btn-success.active,.open .dropdown-toggle.btn-success{background-image:none}.btn-success.disabled,.btn-success[disabled],fieldset[disabled] .btn-success,.btn-success.disabled:hover,.btn-success[disabled]:hover,fieldset[disabled] .btn-success:hover,.btn-success.disabled:focus,.btn-success[disabled]:focus,fieldset[disabled] .btn-success:focus,.btn-success.disabled:active,.btn-success[disabled]:active,fieldset[disabled] .btn-success:active,.btn-success.disabled.active,.btn-success[disabled].active,fieldset[disabled] .btn-success.active{background-color:#5cb85c;border-color:#4cae4c}.btn-success .badge{color:#5cb85c;background-color:#fff}.btn-info{color:#fff;background-color:#5bc0de;border-color:#46b8da}.btn-info:hover,.btn-info:focus,.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{color:#fff;background-color:#39b3d7;border-color:#269abc}.btn-info:active,.btn-info.active,.open .dropdown-toggle.btn-info{background-image:none}.btn-info.disabled,.btn-info[disabled],fieldset[disabled] .btn-info,.btn-info.disabled:hover,.btn-info[disabled]:hover,fieldset[disabled] .btn-info:hover,.btn-info.disabled:focus,.btn-info[disabled]:focus,fieldset[disabled] .btn-info:focus,.btn-info.disabled:active,.btn-info[disabled]:active,fieldset[disabled] .btn-info:active,.btn-info.disabled.active,.btn-info[disabled].active,fieldset[disabled] .btn-info.active{background-color:#5bc0de;border-color:#46b8da}.btn-info .badge{color:#5bc0de;background-color:#fff}.btn-warning{color:#fff;background-color:#f0ad4e;border-color:#eea236}.btn-warning:hover,.btn-warning:focus,.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{color:#fff;background-color:#ed9c28;border-color:#d58512}.btn-warning:active,.btn-warning.active,.open .dropdown-toggle.btn-warning{background-image:none}.btn-warning.disabled,.btn-warning[disabled],fieldset[disabled] .btn-warning,.btn-warning.disabled:hover,.btn-warning[disabled]:hover,fieldset[disabled] .btn-warning:hover,.btn-warning.disabled:focus,.btn-warning[disabled]:focus,fieldset[disabled] .btn-warning:focus,.btn-warning.disabled:active,.btn-warning[disabled]:active,fieldset[disabled] .btn-warning:active,.btn-warning.disabled.active,.btn-warning[disabled].active,fieldset[disabled] .btn-warning.active{background-color:#f0ad4e;border-color:#eea236}.btn-warning .badge{color:#f0ad4e;background-color:#fff}.btn-danger{color:#fff;background-color:#d9534f;border-color:#d43f3a}.btn-danger:hover,.btn-danger:focus,.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{color:#fff;background-color:#d2322d;border-color:#ac2925}.btn-danger:active,.btn-danger.active,.open .dropdown-toggle.btn-danger{background-image:none}.btn-danger.disabled,.btn-danger[disabled],fieldset[disabled] .btn-danger,.btn-danger.disabled:hover,.btn-danger[disabled]:hover,fieldset[disabled] .btn-danger:hover,.btn-danger.disabled:focus,.btn-danger[disabled]:focus,fieldset[disabled] .btn-danger:focus,.btn-danger.disabled:active,.btn-danger[disabled]:active,fieldset[disabled] .btn-danger:active,.btn-danger.disabled.active,.btn-danger[disabled].active,fieldset[disabled] .btn-danger.active{background-color:#d9534f;border-color:#d43f3a}.btn-danger .badge{color:#d9534f;background-color:#fff}.btn-link{color:#428bca;font-weight:400;cursor:pointer;border-radius:0}.btn-link,.btn-link:active,.btn-link[disabled],fieldset[disabled] .btn-link{background-color:transparent;-webkit-box-shadow:none;box-shadow:none}.btn-link,.btn-link:hover,.btn-link:focus,.btn-link:active{border-color:transparent}.btn-link:hover,.btn-link:focus{color:#2a6496;text-decoration:underline;background-color:transparent}.btn-link[disabled]:hover,fieldset[disabled] .btn-link:hover,.btn-link[disabled]:focus,fieldset[disabled] .btn-link:focus{color:#999;text-decoration:none}.btn-lg,.btn-group-lg>.btn{padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}.btn-sm,.btn-group-sm>.btn{padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}.btn-xs,.btn-group-xs>.btn{padding:1px 5px;font-size:12px;line-height:1.5;border-radius:3px}.btn-block{display:block;width:100%;padding-left:0;padding-right:0}.btn-block+.btn-block{margin-top:5px}input[type=submit].btn-block,input[type=reset].btn-block,input[type=button].btn-block{width:100%}.fade{opacity:0;-webkit-transition:opacity .15s linear;transition:opacity .15s linear}.fade.in{opacity:1}.collapse{display:none}.collapse.in{display:block}.collapsing{position:relative;height:0;overflow:hidden;-webkit-transition:height .35s ease;transition:height .35s ease}@font-face{font-family:'Glyphicons Halflings';src:url(../fonts/glyphicons-halflings-regular.eot);src:url(../fonts/glyphicons-halflings-regular.eot?#iefix) format('embedded-opentype'),url(../fonts/glyphicons-halflings-regular.woff) format('woff'),url(../fonts/glyphicons-halflings-regular.ttf) format('truetype'),url(../fonts/glyphicons-halflings-regular.svg#glyphicons_halflingsregular) format('svg')}.glyphicon{position:relative;top:1px;display:inline-block;font-family:'Glyphicons Halflings';font-style:normal;font-weight:400;line-height:1;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.glyphicon-asterisk:before{content:"\2a"}.glyphicon-plus:before{content:"\2b"}.glyphicon-euro:before{content:"\20ac"}.glyphicon-minus:before{content:"\2212"}.glyphicon-cloud:before{content:"\2601"}.glyphicon-envelope:before{content:"\2709"}.glyphicon-pencil:before{content:"\270f"}.glyphicon-glass:before{content:"\e001"}.glyphicon-music:before{content:"\e002"}.glyphicon-search:before{content:"\e003"}.glyphicon-heart:before{content:"\e005"}.glyphicon-star:before{content:"\e006"}.glyphicon-star-empty:before{content:"\e007"}.glyphicon-user:before{content:"\e008"}.glyphicon-film:before{content:"\e009"}.glyphicon-th-large:before{content:"\e010"}.glyphicon-th:before{content:"\e011"}.glyphicon-th-list:before{content:"\e012"}.glyphicon-ok:before{content:"\e013"}.glyphicon-remove:before{content:"\e014"}.glyphicon-zoom-in:before{content:"\e015"}.glyphicon-zoom-out:before{content:"\e016"}.glyphicon-off:before{content:"\e017"}.glyphicon-signal:before{content:"\e018"}.glyphicon-cog:before{content:"\e019"}.glyphicon-trash:before{content:"\e020"}.glyphicon-home:before{content:"\e021"}.glyphicon-file:before{content:"\e022"}.glyphicon-time:before{content:"\e023"}.glyphicon-road:before{content:"\e024"}.glyphicon-download-alt:before{content:"\e025"}.glyphicon-download:before{content:"\e026"}.glyphicon-upload:before{content:"\e027"}.glyphicon-inbox:before{content:"\e028"}.glyphicon-play-circle:before{content:"\e029"}.glyphicon-repeat:before{content:"\e030"}.glyphicon-refresh:before{content:"\e031"}.glyphicon-list-alt:before{content:"\e032"}.glyphicon-lock:before{content:"\e033"}.glyphicon-flag:before{content:"\e034"}.glyphicon-headphones:before{content:"\e035"}.glyphicon-volume-off:before{content:"\e036"}.glyphicon-volume-down:before{content:"\e037"}.glyphicon-volume-up:before{content:"\e038"}.glyphicon-qrcode:before{content:"\e039"}.glyphicon-barcode:before{content:"\e040"}.glyphicon-tag:before{content:"\e041"}.glyphicon-tags:before{content:"\e042"}.glyphicon-book:before{content:"\e043"}.glyphicon-bookmark:before{content:"\e044"}.glyphicon-print:before{content:"\e045"}.glyphicon-camera:before{content:"\e046"}.glyphicon-font:before{content:"\e047"}.glyphicon-bold:before{content:"\e048"}.glyphicon-italic:before{content:"\e049"}.glyphicon-text-height:before{content:"\e050"}.glyphicon-text-width:before{content:"\e051"}.glyphicon-align-left:before{content:"\e052"}.glyphicon-align-center:before{content:"\e053"}.glyphicon-align-right:before{content:"\e054"}.glyphicon-align-justify:before{content:"\e055"}.glyphicon-list:before{content:"\e056"}.glyphicon-indent-left:before{content:"\e057"}.glyphicon-indent-right:before{content:"\e058"}.glyphicon-facetime-video:before{content:"\e059"}.glyphicon-picture:before{content:"\e060"}.glyphicon-map-marker:before{content:"\e062"}.glyphicon-adjust:before{content:"\e063"}.glyphicon-tint:before{content:"\e064"}.glyphicon-edit:before{content:"\e065"}.glyphicon-share:before{content:"\e066"}.glyphicon-check:before{content:"\e067"}.glyphicon-move:before{content:"\e068"}.glyphicon-step-backward:before{content:"\e069"}.glyphicon-fast-backward:before{content:"\e070"}.glyphicon-backward:before{content:"\e071"}.glyphicon-play:before{content:"\e072"}.glyphicon-pause:before{content:"\e073"}.glyphicon-stop:before{content:"\e074"}.glyphicon-forward:before{content:"\e075"}.glyphicon-fast-forward:before{content:"\e076"}.glyphicon-step-forward:before{content:"\e077"}.glyphicon-eject:before{content:"\e078"}.glyphicon-chevron-left:before{content:"\e079"}.glyphicon-chevron-right:before{content:"\e080"}.glyphicon-plus-sign:before{content:"\e081"}.glyphicon-minus-sign:before{content:"\e082"}.glyphicon-remove-sign:before{content:"\e083"}.glyphicon-ok-sign:before{content:"\e084"}.glyphicon-question-sign:before{content:"\e085"}.glyphicon-info-sign:before{content:"\e086"}.glyphicon-screenshot:before{content:"\e087"}.glyphicon-remove-circle:before{content:"\e088"}.glyphicon-ok-circle:before{content:"\e089"}.glyphicon-ban-circle:before{content:"\e090"}.glyphicon-arrow-left:before{content:"\e091"}.glyphicon-arrow-right:before{content:"\e092"}.glyphicon-arrow-up:before{content:"\e093"}.glyphicon-arrow-down:before{content:"\e094"}.glyphicon-share-alt:before{content:"\e095"}.glyphicon-resize-full:before{content:"\e096"}.glyphicon-resize-small:before{content:"\e097"}.glyphicon-exclamation-sign:before{content:"\e101"}.glyphicon-gift:before{content:"\e102"}.glyphicon-leaf:before{content:"\e103"}.glyphicon-fire:before{content:"\e104"}.glyphicon-eye-open:before{content:"\e105"}.glyphicon-eye-close:before{content:"\e106"}.glyphicon-warning-sign:before{content:"\e107"}.glyphicon-plane:before{content:"\e108"}.glyphicon-calendar:before{content:"\e109"}.glyphicon-random:before{content:"\e110"}.glyphicon-comment:before{content:"\e111"}.glyphicon-magnet:before{content:"\e112"}.glyphicon-chevron-up:before{content:"\e113"}.glyphicon-chevron-down:before{content:"\e114"}.glyphicon-retweet:before{content:"\e115"}.glyphicon-shopping-cart:before{content:"\e116"}.glyphicon-folder-close:before{content:"\e117"}.glyphicon-folder-open:before{content:"\e118"}.glyphicon-resize-vertical:before{content:"\e119"}.glyphicon-resize-horizontal:before{content:"\e120"}.glyphicon-hdd:before{content:"\e121"}.glyphicon-bullhorn:before{content:"\e122"}.glyphicon-bell:before{content:"\e123"}.glyphicon-certificate:before{content:"\e124"}.glyphicon-thumbs-up:before{content:"\e125"}.glyphicon-thumbs-down:before{content:"\e126"}.glyphicon-hand-right:before{content:"\e127"}.glyphicon-hand-left:before{content:"\e128"}.glyphicon-hand-up:before{content:"\e129"}.glyphicon-hand-down:before{content:"\e130"}.glyphicon-circle-arrow-right:before{content:"\e131"}.glyphicon-circle-arrow-left:before{content:"\e132"}.glyphicon-circle-arrow-up:before{content:"\e133"}.glyphicon-circle-arrow-down:before{content:"\e134"}.glyphicon-globe:before{content:"\e135"}.glyphicon-wrench:before{content:"\e136"}.glyphicon-tasks:before{content:"\e137"}.glyphicon-filter:before{content:"\e138"}.glyphicon-briefcase:before{content:"\e139"}.glyphicon-fullscreen:before{content:"\e140"}.glyphicon-dashboard:before{content:"\e141"}.glyphicon-paperclip:before{content:"\e142"}.glyphicon-heart-empty:before{content:"\e143"}.glyphicon-link:before{content:"\e144"}.glyphicon-phone:before{content:"\e145"}.glyphicon-pushpin:before{content:"\e146"}.glyphicon-usd:before{content:"\e148"}.glyphicon-gbp:before{content:"\e149"}.glyphicon-sort:before{content:"\e150"}.glyphicon-sort-by-alphabet:before{content:"\e151"}.glyphicon-sort-by-alphabet-alt:before{content:"\e152"}.glyphicon-sort-by-order:before{content:"\e153"}.glyphicon-sort-by-order-alt:before{content:"\e154"}.glyphicon-sort-by-attributes:before{content:"\e155"}.glyphicon-sort-by-attributes-alt:before{content:"\e156"}.glyphicon-unchecked:before{content:"\e157"}.glyphicon-expand:before{content:"\e158"}.glyphicon-collapse-down:before{content:"\e159"}.glyphicon-collapse-up:before{content:"\e160"}.glyphicon-log-in:before{content:"\e161"}.glyphicon-flash:before{content:"\e162"}.glyphicon-log-out:before{content:"\e163"}.glyphicon-new-window:before{content:"\e164"}.glyphicon-record:before{content:"\e165"}.glyphicon-save:before{content:"\e166"}.glyphicon-open:before{content:"\e167"}.glyphicon-saved:before{content:"\e168"}.glyphicon-import:before{content:"\e169"}.glyphicon-export:before{content:"\e170"}.glyphicon-send:before{content:"\e171"}.glyphicon-floppy-disk:before{content:"\e172"}.glyphicon-floppy-saved:before{content:"\e173"}.glyphicon-floppy-remove:before{content:"\e174"}.glyphicon-floppy-save:before{content:"\e175"}.glyphicon-floppy-open:before{content:"\e176"}.glyphicon-credit-card:before{content:"\e177"}.glyphicon-transfer:before{content:"\e178"}.glyphicon-cutlery:before{content:"\e179"}.glyphicon-header:before{content:"\e180"}.glyphicon-compressed:before{content:"\e181"}.glyphicon-earphone:before{content:"\e182"}.glyphicon-phone-alt:before{content:"\e183"}.glyphicon-tower:before{content:"\e184"}.glyphicon-stats:before{content:"\e185"}.glyphicon-sd-video:before{content:"\e186"}.glyphicon-hd-video:before{content:"\e187"}.glyphicon-subtitles:before{content:"\e188"}.glyphicon-sound-stereo:before{content:"\e189"}.glyphicon-sound-dolby:before{content:"\e190"}.glyphicon-sound-5-1:before{content:"\e191"}.glyphicon-sound-6-1:before{content:"\e192"}.glyphicon-sound-7-1:before{content:"\e193"}.glyphicon-copyright-mark:before{content:"\e194"}.glyphicon-registration-mark:before{content:"\e195"}.glyphicon-cloud-download:before{content:"\e197"}.glyphicon-cloud-upload:before{content:"\e198"}.glyphicon-tree-conifer:before{content:"\e199"}.glyphicon-tree-deciduous:before{content:"\e200"}.caret{display:inline-block;width:0;height:0;margin-left:2px;vertical-align:middle;border-top:4px solid;border-right:4px solid transparent;border-left:4px solid transparent}.dropdown{position:relative}.dropdown-toggle:focus{outline:0}.dropdown-menu{position:absolute;top:100%;left:0;z-index:1000;display:none;float:left;min-width:160px;padding:5px 0;margin:2px 0 0;list-style:none;font-size:14px;background-color:#fff;border:1px solid #ccc;border:1px solid rgba(0,0,0,.15);border-radius:4px;-webkit-box-shadow:0 6px 12px rgba(0,0,0,.175);box-shadow:0 6px 12px rgba(0,0,0,.175);background-clip:padding-box}.dropdown-menu.pull-right{right:0;left:auto}.dropdown-menu .divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.dropdown-menu>li>a{display:block;padding:3px 20px;clear:both;font-weight:400;line-height:1.42857143;color:#333;white-space:nowrap}.dropdown-menu>li>a:hover,.dropdown-menu>li>a:focus{text-decoration:none;color:#262626;background-color:#f5f5f5}.dropdown-menu>.active>a,.dropdown-menu>.active>a:hover,.dropdown-menu>.active>a:focus{color:#fff;text-decoration:none;outline:0;background-color:#428bca}.dropdown-menu>.disabled>a,.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{color:#999}.dropdown-menu>.disabled>a:hover,.dropdown-menu>.disabled>a:focus{text-decoration:none;background-color:transparent;background-image:none;filter:progid:DXImageTransform.Microsoft.gradient(enabled=false);cursor:not-allowed}.open>.dropdown-menu{display:block}.open>a{outline:0}.dropdown-menu-right{left:auto;right:0}.dropdown-menu-left{left:0;right:auto}.dropdown-header{display:block;padding:3px 20px;font-size:12px;line-height:1.42857143;color:#999}.dropdown-backdrop{position:fixed;left:0;right:0;bottom:0;top:0;z-index:990}.pull-right>.dropdown-menu{right:0;left:auto}.dropup .caret,.navbar-fixed-bottom .dropdown .caret{border-top:0;border-bottom:4px solid;content:""}.dropup .dropdown-menu,.navbar-fixed-bottom .dropdown .dropdown-menu{top:auto;bottom:100%;margin-bottom:1px}@media (min-width:768px){.navbar-right .dropdown-menu{left:auto;right:0}.navbar-right .dropdown-menu-left{left:0;right:auto}}.btn-group,.btn-group-vertical{position:relative;display:inline-block;vertical-align:middle}.btn-group>.btn,.btn-group-vertical>.btn{position:relative;float:left}.btn-group>.btn:hover,.btn-group-vertical>.btn:hover,.btn-group>.btn:focus,.btn-group-vertical>.btn:focus,.btn-group>.btn:active,.btn-group-vertical>.btn:active,.btn-group>.btn.active,.btn-group-vertical>.btn.active{z-index:2}.btn-group>.btn:focus,.btn-group-vertical>.btn:focus{outline:0}.btn-group .btn+.btn,.btn-group .btn+.btn-group,.btn-group .btn-group+.btn,.btn-group .btn-group+.btn-group{margin-left:-1px}.btn-toolbar{margin-left:-5px}.btn-toolbar .btn-group,.btn-toolbar .input-group{float:left}.btn-toolbar>.btn,.btn-toolbar>.btn-group,.btn-toolbar>.input-group{margin-left:5px}.btn-group>.btn:not(:first-child):not(:last-child):not(.dropdown-toggle){border-radius:0}.btn-group>.btn:first-child{margin-left:0}.btn-group>.btn:first-child:not(:last-child):not(.dropdown-toggle){border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn:last-child:not(:first-child),.btn-group>.dropdown-toggle:not(:first-child){border-bottom-left-radius:0;border-top-left-radius:0}.btn-group>.btn-group{float:left}.btn-group>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group>.btn-group:first-child>.btn:last-child,.btn-group>.btn-group:first-child>.dropdown-toggle{border-bottom-right-radius:0;border-top-right-radius:0}.btn-group>.btn-group:last-child>.btn:first-child{border-bottom-left-radius:0;border-top-left-radius:0}.btn-group .dropdown-toggle:active,.btn-group.open .dropdown-toggle{outline:0}.btn-group>.btn+.dropdown-toggle{padding-left:8px;padding-right:8px}.btn-group>.btn-lg+.dropdown-toggle{padding-left:12px;padding-right:12px}.btn-group.open .dropdown-toggle{-webkit-box-shadow:inset 0 3px 5px rgba(0,0,0,.125);box-shadow:inset 0 3px 5px rgba(0,0,0,.125)}.btn-group.open .dropdown-toggle.btn-link{-webkit-box-shadow:none;box-shadow:none}.btn .caret{margin-left:0}.btn-lg .caret{border-width:5px 5px 0;border-bottom-width:0}.dropup .btn-lg .caret{border-width:0 5px 5px}.btn-group-vertical>.btn,.btn-group-vertical>.btn-group,.btn-group-vertical>.btn-group>.btn{display:block;float:none;width:100%;max-width:100%}.btn-group-vertical>.btn-group>.btn{float:none}.btn-group-vertical>.btn+.btn,.btn-group-vertical>.btn+.btn-group,.btn-group-vertical>.btn-group+.btn,.btn-group-vertical>.btn-group+.btn-group{margin-top:-1px;margin-left:0}.btn-group-vertical>.btn:not(:first-child):not(:last-child){border-radius:0}.btn-group-vertical>.btn:first-child:not(:last-child){border-top-right-radius:4px;border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn:last-child:not(:first-child){border-bottom-left-radius:4px;border-top-right-radius:0;border-top-left-radius:0}.btn-group-vertical>.btn-group:not(:first-child):not(:last-child)>.btn{border-radius:0}.btn-group-vertical>.btn-group:first-child:not(:last-child)>.btn:last-child,.btn-group-vertical>.btn-group:first-child:not(:last-child)>.dropdown-toggle{border-bottom-right-radius:0;border-bottom-left-radius:0}.btn-group-vertical>.btn-group:last-child:not(:first-child)>.btn:first-child{border-top-right-radius:0;border-top-left-radius:0}.btn-group-justified{display:table;width:100%;table-layout:fixed;border-collapse:separate}.btn-group-justified>.btn,.btn-group-justified>.btn-group{float:none;display:table-cell;width:1%}.btn-group-justified>.btn-group .btn{width:100%}[data-toggle=buttons]>.btn>input[type=radio],[data-toggle=buttons]>.btn>input[type=checkbox]{display:none}.input-group{position:relative;display:table;border-collapse:separate}.input-group[class*=col-]{float:none;padding-left:0;padding-right:0}.input-group .form-control{position:relative;z-index:2;float:left;width:100%;margin-bottom:0}.input-group-lg>.form-control,.input-group-lg>.input-group-addon,.input-group-lg>.input-group-btn>.btn{height:46px;padding:10px 16px;font-size:18px;line-height:1.33;border-radius:6px}select.input-group-lg>.form-control,select.input-group-lg>.input-group-addon,select.input-group-lg>.input-group-btn>.btn{height:46px;line-height:46px}textarea.input-group-lg>.form-control,textarea.input-group-lg>.input-group-addon,textarea.input-group-lg>.input-group-btn>.btn,select[multiple].input-group-lg>.form-control,select[multiple].input-group-lg>.input-group-addon,select[multiple].input-group-lg>.input-group-btn>.btn{height:auto}.input-group-sm>.form-control,.input-group-sm>.input-group-addon,.input-group-sm>.input-group-btn>.btn{height:30px;padding:5px 10px;font-size:12px;line-height:1.5;border-radius:3px}select.input-group-sm>.form-control,select.input-group-sm>.input-group-addon,select.input-group-sm>.input-group-btn>.btn{height:30px;line-height:30px}textarea.input-group-sm>.form-control,textarea.input-group-sm>.input-group-addon,textarea.input-group-sm>.input-group-btn>.btn,select[multiple].input-group-sm>.form-control,select[multiple].input-group-sm>.input-group-addon,select[multiple].input-group-sm>.input-group-btn>.btn{height:auto}.input-group-addon,.input-group-btn,.input-group .form-control{display:table-cell}.input-group-addon:not(:first-child):not(:last-child),.input-group-btn:not(:first-child):not(:last-child),.input-group .form-control:not(:first-child):not(:last-child){border-radius:0}.input-group-addon,.input-group-btn{width:1%;white-space:nowrap;vertical-align:middle}.input-group-addon{padding:6px 12px;font-size:14px;font-weight:400;line-height:1;color:#555;text-align:center;background-color:#eee;border:1px solid #ccc;border-radius:4px}.input-group-addon.input-sm{padding:5px 10px;font-size:12px;border-radius:3px}.input-group-addon.input-lg{padding:10px 16px;font-size:18px;border-radius:6px}.input-group-addon input[type=radio],.input-group-addon input[type=checkbox]{margin-top:0}.input-group .form-control:first-child,.input-group-addon:first-child,.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group>.btn,.input-group-btn:first-child>.dropdown-toggle,.input-group-btn:last-child>.btn:not(:last-child):not(.dropdown-toggle),.input-group-btn:last-child>.btn-group:not(:last-child)>.btn{border-bottom-right-radius:0;border-top-right-radius:0}.input-group-addon:first-child{border-right:0}.input-group .form-control:last-child,.input-group-addon:last-child,.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group>.btn,.input-group-btn:last-child>.dropdown-toggle,.input-group-btn:first-child>.btn:not(:first-child),.input-group-btn:first-child>.btn-group:not(:first-child)>.btn{border-bottom-left-radius:0;border-top-left-radius:0}.input-group-addon:last-child{border-left:0}.input-group-btn{position:relative;font-size:0;white-space:nowrap}.input-group-btn>.btn{position:relative}.input-group-btn>.btn+.btn{margin-left:-1px}.input-group-btn>.btn:hover,.input-group-btn>.btn:focus,.input-group-btn>.btn:active{z-index:2}.input-group-btn:first-child>.btn,.input-group-btn:first-child>.btn-group{margin-right:-1px}.input-group-btn:last-child>.btn,.input-group-btn:last-child>.btn-group{margin-left:-1px}.nav{margin-bottom:0;padding-left:0;list-style:none}.nav>li{position:relative;display:block}.nav>li>a{position:relative;display:block;padding:10px 15px}.nav>li>a:hover,.nav>li>a:focus{text-decoration:none;background-color:#eee}.nav>li.disabled>a{color:#999}.nav>li.disabled>a:hover,.nav>li.disabled>a:focus{color:#999;text-decoration:none;background-color:transparent;cursor:not-allowed}.nav .open>a,.nav .open>a:hover,.nav .open>a:focus{background-color:#eee;border-color:#428bca}.nav .nav-divider{height:1px;margin:9px 0;overflow:hidden;background-color:#e5e5e5}.nav>li>a>img{max-width:none}.nav-tabs{border-bottom:1px solid #ddd}.nav-tabs>li{float:left;margin-bottom:-1px}.nav-tabs>li>a{margin-right:2px;line-height:1.42857143;border:1px solid transparent;border-radius:4px 4px 0 0}.nav-tabs>li>a:hover{border-color:#eee #eee #ddd}.nav-tabs>li.active>a,.nav-tabs>li.active>a:hover,.nav-tabs>li.active>a:focus{color:#555;background-color:#fff;border:1px solid #ddd;border-bottom-color:transparent;cursor:default}.nav-tabs.nav-justified{width:100%;border-bottom:0}.nav-tabs.nav-justified>li{float:none}.nav-tabs.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-tabs.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-tabs.nav-justified>li{display:table-cell;width:1%}.nav-tabs.nav-justified>li>a{margin-bottom:0}}.nav-tabs.nav-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs.nav-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs.nav-justified>.active>a,.nav-tabs.nav-justified>.active>a:hover,.nav-tabs.nav-justified>.active>a:focus{border-bottom-color:#fff}}.nav-pills>li{float:left}.nav-pills>li>a{border-radius:4px}.nav-pills>li+li{margin-left:2px}.nav-pills>li.active>a,.nav-pills>li.active>a:hover,.nav-pills>li.active>a:focus{color:#fff;background-color:#428bca}.nav-stacked>li{float:none}.nav-stacked>li+li{margin-top:2px;margin-left:0}.nav-justified{width:100%}.nav-justified>li{float:none}.nav-justified>li>a{text-align:center;margin-bottom:5px}.nav-justified>.dropdown .dropdown-menu{top:auto;left:auto}@media (min-width:768px){.nav-justified>li{display:table-cell;width:1%}.nav-justified>li>a{margin-bottom:0}}.nav-tabs-justified{border-bottom:0}.nav-tabs-justified>li>a{margin-right:0;border-radius:4px}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border:1px solid #ddd}@media (min-width:768px){.nav-tabs-justified>li>a{border-bottom:1px solid #ddd;border-radius:4px 4px 0 0}.nav-tabs-justified>.active>a,.nav-tabs-justified>.active>a:hover,.nav-tabs-justified>.active>a:focus{border-bottom-color:#fff}}.tab-content>.tab-pane{display:none}.tab-content>.active{display:block}.nav-tabs .dropdown-menu{margin-top:-1px;border-top-right-radius:0;border-top-left-radius:0}.navbar{position:relative;min-height:50px;margin-bottom:20px;border:1px solid transparent}@media (min-width:768px){.navbar{border-radius:4px}}@media (min-width:768px){.navbar-header{float:left}}.navbar-collapse{max-height:340px;overflow-x:visible;padding-right:15px;padding-left:15px;border-top:1px solid transparent;box-shadow:inset 0 1px 0 rgba(255,255,255,.1);-webkit-overflow-scrolling:touch}.navbar-collapse.in{overflow-y:auto}@media (min-width:768px){.navbar-collapse{width:auto;border-top:0;box-shadow:none}.navbar-collapse.collapse{display:block!important;height:auto!important;padding-bottom:0;overflow:visible!important}.navbar-collapse.in{overflow-y:visible}.navbar-fixed-top .navbar-collapse,.navbar-static-top .navbar-collapse,.navbar-fixed-bottom .navbar-collapse{padding-left:0;padding-right:0}}.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:-15px;margin-left:-15px}@media (min-width:768px){.container>.navbar-header,.container-fluid>.navbar-header,.container>.navbar-collapse,.container-fluid>.navbar-collapse{margin-right:0;margin-left:0}}.navbar-static-top{z-index:1000;border-width:0 0 1px}@media (min-width:768px){.navbar-static-top{border-radius:0}}.navbar-fixed-top,.navbar-fixed-bottom{position:fixed;right:0;left:0;z-index:1030}@media (min-width:768px){.navbar-fixed-top,.navbar-fixed-bottom{border-radius:0}}.navbar-fixed-top{top:0;border-width:0 0 1px}.navbar-fixed-bottom{bottom:0;margin-bottom:0;border-width:1px 0 0}.navbar-brand{float:left;padding:15px;font-size:18px;line-height:20px;height:50px}.navbar-brand:hover,.navbar-brand:focus{text-decoration:none}@media (min-width:768px){.navbar>.container .navbar-brand,.navbar>.container-fluid .navbar-brand{margin-left:-15px}}.navbar-toggle{position:relative;float:right;margin-right:15px;padding:9px 10px;margin-top:8px;margin-bottom:8px;background-color:transparent;background-image:none;border:1px solid transparent;border-radius:4px}.navbar-toggle:focus{outline:0}.navbar-toggle .icon-bar{display:block;width:22px;height:2px;border-radius:1px}.navbar-toggle .icon-bar+.icon-bar{margin-top:4px}@media (min-width:768px){.navbar-toggle{display:none}}.navbar-nav{margin:7.5px -15px}.navbar-nav>li>a{padding-top:10px;padding-bottom:10px;line-height:20px}@media (max-width:767px){.navbar-nav .open .dropdown-menu{position:static;float:none;width:auto;margin-top:0;background-color:transparent;border:0;box-shadow:none}.navbar-nav .open .dropdown-menu>li>a,.navbar-nav .open .dropdown-menu .dropdown-header{padding:5px 15px 5px 25px}.navbar-nav .open .dropdown-menu>li>a{line-height:20px}.navbar-nav .open .dropdown-menu>li>a:hover,.navbar-nav .open .dropdown-menu>li>a:focus{background-image:none}}@media (min-width:768px){.navbar-nav{float:left;margin:0}.navbar-nav>li{float:left}.navbar-nav>li>a{padding-top:15px;padding-bottom:15px}.navbar-nav.navbar-right:last-child{margin-right:-15px}}@media (min-width:768px){.navbar-left{float:left!important}.navbar-right{float:right!important}}.navbar-form{margin-left:-15px;margin-right:-15px;padding:10px 15px;border-top:1px solid transparent;border-bottom:1px solid transparent;-webkit-box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);box-shadow:inset 0 1px 0 rgba(255,255,255,.1),0 1px 0 rgba(255,255,255,.1);margin-top:8px;margin-bottom:8px}@media (min-width:768px){.navbar-form .form-group{display:inline-block;margin-bottom:0;vertical-align:middle}.navbar-form .form-control{display:inline-block;width:auto;vertical-align:middle}.navbar-form .input-group>.form-control{width:100%}.navbar-form .control-label{margin-bottom:0;vertical-align:middle}.navbar-form .radio,.navbar-form .checkbox{display:inline-block;margin-top:0;margin-bottom:0;padding-left:0;vertical-align:middle}.navbar-form .radio input[type=radio],.navbar-form .checkbox input[type=checkbox]{float:none;margin-left:0}.navbar-form .has-feedback .form-control-feedback{top:0}}@media (max-width:767px){.navbar-form .form-group{margin-bottom:5px}}@media (min-width:768px){.navbar-form{width:auto;border:0;margin-left:0;margin-right:0;padding-top:0;padding-bottom:0;-webkit-box-shadow:none;box-shadow:none}.navbar-form.navbar-right:last-child{margin-right:-15px}}.navbar-nav>li>.dropdown-menu{margin-top:0;border-top-right-radius:0;border-top-left-radius:0}.navbar-fixed-bottom .navbar-nav>li>.dropdown-menu{border-bottom-right-radius:0;border-bottom-left-radius:0}.navbar-btn{margin-top:8px;margin-bottom:8px}.navbar-btn.btn-sm{margin-top:10px;margin-bottom:10px}.navbar-btn.btn-xs{margin-top:14px;margin-bottom:14px}.navbar-text{margin-top:15px;margin-bottom:15px}@media (min-width:768px){.navbar-text{float:left;margin-left:15px;margin-right:15px}.navbar-text.navbar-right:last-child{margin-right:0}}.navbar-default{background-color:#f8f8f8;border-color:#e7e7e7}.navbar-default .navbar-brand{color:#777}.navbar-default .navbar-brand:hover,.navbar-default .navbar-brand:focus{color:#5e5e5e;background-color:transparent}.navbar-default .navbar-text{color:#777}.navbar-default .navbar-nav>li>a{color:#777}.navbar-default .navbar-nav>li>a:hover,.navbar-default .navbar-nav>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav>.active>a,.navbar-default .navbar-nav>.active>a:hover,.navbar-default .navbar-nav>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav>.disabled>a,.navbar-default .navbar-nav>.disabled>a:hover,.navbar-default .navbar-nav>.disabled>a:focus{color:#ccc;background-color:transparent}.navbar-default .navbar-toggle{border-color:#ddd}.navbar-default .navbar-toggle:hover,.navbar-default .navbar-toggle:focus{background-color:#ddd}.navbar-default .navbar-toggle .icon-bar{background-color:#888}.navbar-default .navbar-collapse,.navbar-default .navbar-form{border-color:#e7e7e7}.navbar-default .navbar-nav>.open>a,.navbar-default .navbar-nav>.open>a:hover,.navbar-default .navbar-nav>.open>a:focus{background-color:#e7e7e7;color:#555}@media (max-width:767px){.navbar-default .navbar-nav .open .dropdown-menu>li>a{color:#777}.navbar-default .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>li>a:focus{color:#333;background-color:transparent}.navbar-default .navbar-nav .open .dropdown-menu>.active>a,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.active>a:focus{color:#555;background-color:#e7e7e7}.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-default .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#ccc;background-color:transparent}}.navbar-default .navbar-link{color:#777}.navbar-default .navbar-link:hover{color:#333}.navbar-inverse{background-color:#222;border-color:#080808}.navbar-inverse .navbar-brand{color:#999}.navbar-inverse .navbar-brand:hover,.navbar-inverse .navbar-brand:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-text{color:#999}.navbar-inverse .navbar-nav>li>a{color:#999}.navbar-inverse .navbar-nav>li>a:hover,.navbar-inverse .navbar-nav>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav>.active>a,.navbar-inverse .navbar-nav>.active>a:hover,.navbar-inverse .navbar-nav>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav>.disabled>a,.navbar-inverse .navbar-nav>.disabled>a:hover,.navbar-inverse .navbar-nav>.disabled>a:focus{color:#444;background-color:transparent}.navbar-inverse .navbar-toggle{border-color:#333}.navbar-inverse .navbar-toggle:hover,.navbar-inverse .navbar-toggle:focus{background-color:#333}.navbar-inverse .navbar-toggle .icon-bar{background-color:#fff}.navbar-inverse .navbar-collapse,.navbar-inverse .navbar-form{border-color:#101010}.navbar-inverse .navbar-nav>.open>a,.navbar-inverse .navbar-nav>.open>a:hover,.navbar-inverse .navbar-nav>.open>a:focus{background-color:#080808;color:#fff}@media (max-width:767px){.navbar-inverse .navbar-nav .open .dropdown-menu>.dropdown-header{border-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu .divider{background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a{color:#999}.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>li>a:focus{color:#fff;background-color:transparent}.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.active>a:focus{color:#fff;background-color:#080808}.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:hover,.navbar-inverse .navbar-nav .open .dropdown-menu>.disabled>a:focus{color:#444;background-color:transparent}}.navbar-inverse .navbar-link{color:#999}.navbar-inverse .navbar-link:hover{color:#fff}.breadcrumb{padding:8px 15px;margin-bottom:20px;list-style:none;background-color:#f5f5f5;border-radius:4px}.breadcrumb>li{display:inline-block}.breadcrumb>li+li:before{content:"/\00a0";padding:0 5px;color:#ccc}.breadcrumb>.active{color:#999}.pagination{display:inline-block;padding-left:0;margin:20px 0;border-radius:4px}.pagination>li{display:inline}.pagination>li>a,.pagination>li>span{position:relative;float:left;padding:6px 12px;line-height:1.42857143;text-decoration:none;color:#428bca;background-color:#fff;border:1px solid #ddd;margin-left:-1px}.pagination>li:first-child>a,.pagination>li:first-child>span{margin-left:0;border-bottom-left-radius:4px;border-top-left-radius:4px}.pagination>li:last-child>a,.pagination>li:last-child>span{border-bottom-right-radius:4px;border-top-right-radius:4px}.pagination>li>a:hover,.pagination>li>span:hover,.pagination>li>a:focus,.pagination>li>span:focus{color:#2a6496;background-color:#eee;border-color:#ddd}.pagination>.active>a,.pagination>.active>span,.pagination>.active>a:hover,.pagination>.active>span:hover,.pagination>.active>a:focus,.pagination>.active>span:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca;cursor:default}.pagination>.disabled>span,.pagination>.disabled>span:hover,.pagination>.disabled>span:focus,.pagination>.disabled>a,.pagination>.disabled>a:hover,.pagination>.disabled>a:focus{color:#999;background-color:#fff;border-color:#ddd;cursor:not-allowed}.pagination-lg>li>a,.pagination-lg>li>span{padding:10px 16px;font-size:18px}.pagination-lg>li:first-child>a,.pagination-lg>li:first-child>span{border-bottom-left-radius:6px;border-top-left-radius:6px}.pagination-lg>li:last-child>a,.pagination-lg>li:last-child>span{border-bottom-right-radius:6px;border-top-right-radius:6px}.pagination-sm>li>a,.pagination-sm>li>span{padding:5px 10px;font-size:12px}.pagination-sm>li:first-child>a,.pagination-sm>li:first-child>span{border-bottom-left-radius:3px;border-top-left-radius:3px}.pagination-sm>li:last-child>a,.pagination-sm>li:last-child>span{border-bottom-right-radius:3px;border-top-right-radius:3px}.pager{padding-left:0;margin:20px 0;list-style:none;text-align:center}.pager li{display:inline}.pager li>a,.pager li>span{display:inline-block;padding:5px 14px;background-color:#fff;border:1px solid #ddd;border-radius:15px}.pager li>a:hover,.pager li>a:focus{text-decoration:none;background-color:#eee}.pager .next>a,.pager .next>span{float:right}.pager .previous>a,.pager .previous>span{float:left}.pager .disabled>a,.pager .disabled>a:hover,.pager .disabled>a:focus,.pager .disabled>span{color:#999;background-color:#fff;cursor:not-allowed}.label{display:inline;padding:.2em .6em .3em;font-size:75%;font-weight:700;line-height:1;color:#fff;text-align:center;white-space:nowrap;vertical-align:baseline;border-radius:.25em}.label[href]:hover,.label[href]:focus{color:#fff;text-decoration:none;cursor:pointer}.label:empty{display:none}.btn .label{position:relative;top:-1px}.label-default{background-color:#999}.label-default[href]:hover,.label-default[href]:focus{background-color:gray}.label-primary{background-color:#428bca}.label-primary[href]:hover,.label-primary[href]:focus{background-color:#3071a9}.label-success{background-color:#5cb85c}.label-success[href]:hover,.label-success[href]:focus{background-color:#449d44}.label-info{background-color:#5bc0de}.label-info[href]:hover,.label-info[href]:focus{background-color:#31b0d5}.label-warning{background-color:#f0ad4e}.label-warning[href]:hover,.label-warning[href]:focus{background-color:#ec971f}.label-danger{background-color:#d9534f}.label-danger[href]:hover,.label-danger[href]:focus{background-color:#c9302c}.badge{display:inline-block;min-width:10px;padding:3px 7px;font-size:12px;font-weight:700;color:#fff;line-height:1;vertical-align:baseline;white-space:nowrap;text-align:center;background-color:#999;border-radius:10px}.badge:empty{display:none}.btn .badge{position:relative;top:-1px}.btn-xs .badge{top:0;padding:1px 5px}a.badge:hover,a.badge:focus{color:#fff;text-decoration:none;cursor:pointer}a.list-group-item.active>.badge,.nav-pills>.active>a>.badge{color:#428bca;background-color:#fff}.nav-pills>li>a>.badge{margin-left:3px}.jumbotron{padding:30px;margin-bottom:30px;color:inherit;background-color:#eee}.jumbotron h1,.jumbotron .h1{color:inherit}.jumbotron p{margin-bottom:15px;font-size:21px;font-weight:200}.container .jumbotron{border-radius:6px}.jumbotron .container{max-width:100%}@media screen and (min-width:768px){.jumbotron{padding-top:48px;padding-bottom:48px}.container .jumbotron{padding-left:60px;padding-right:60px}.jumbotron h1,.jumbotron .h1{font-size:63px}}.thumbnail{display:block;padding:4px;margin-bottom:20px;line-height:1.42857143;background-color:#fff;border:1px solid #ddd;border-radius:4px;-webkit-transition:all .2s ease-in-out;transition:all .2s ease-in-out}.thumbnail>img,.thumbnail a>img{margin-left:auto;margin-right:auto}a.thumbnail:hover,a.thumbnail:focus,a.thumbnail.active{border-color:#428bca}.thumbnail .caption{padding:9px;color:#333}.alert{padding:15px;margin-bottom:20px;border:1px solid transparent;border-radius:4px}.alert h4{margin-top:0;color:inherit}.alert .alert-link{font-weight:700}.alert>p,.alert>ul{margin-bottom:0}.alert>p+p{margin-top:5px}.alert-dismissable{padding-right:35px}.alert-dismissable .close{position:relative;top:-2px;right:-21px;color:inherit}.alert-success{background-color:#dff0d8;border-color:#d6e9c6;color:#3c763d}.alert-success hr{border-top-color:#c9e2b3}.alert-success .alert-link{color:#2b542c}.alert-info{background-color:#d9edf7;border-color:#bce8f1;color:#31708f}.alert-info hr{border-top-color:#a6e1ec}.alert-info .alert-link{color:#245269}.alert-warning{background-color:#fcf8e3;border-color:#faebcc;color:#8a6d3b}.alert-warning hr{border-top-color:#f7e1b5}.alert-warning .alert-link{color:#66512c}.alert-danger{background-color:#f2dede;border-color:#ebccd1;color:#a94442}.alert-danger hr{border-top-color:#e4b9c0}.alert-danger .alert-link{color:#843534}@-webkit-keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}@keyframes progress-bar-stripes{from{background-position:40px 0}to{background-position:0 0}}.progress{overflow:hidden;height:20px;margin-bottom:20px;background-color:#f5f5f5;border-radius:4px;-webkit-box-shadow:inset 0 1px 2px rgba(0,0,0,.1);box-shadow:inset 0 1px 2px rgba(0,0,0,.1)}.progress-bar{float:left;width:0;height:100%;font-size:12px;line-height:20px;color:#fff;text-align:center;background-color:#428bca;-webkit-box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);box-shadow:inset 0 -1px 0 rgba(0,0,0,.15);-webkit-transition:width .6s ease;transition:width .6s ease}.progress-striped .progress-bar{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-size:40px 40px}.progress.active .progress-bar{-webkit-animation:progress-bar-stripes 2s linear infinite;animation:progress-bar-stripes 2s linear infinite}.progress-bar-success{background-color:#5cb85c}.progress-striped .progress-bar-success{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-info{background-color:#5bc0de}.progress-striped .progress-bar-info{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-warning{background-color:#f0ad4e}.progress-striped .progress-bar-warning{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.progress-bar-danger{background-color:#d9534f}.progress-striped .progress-bar-danger{background-image:-webkit-linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent);background-image:linear-gradient(45deg,rgba(255,255,255,.15) 25%,transparent 25%,transparent 50%,rgba(255,255,255,.15) 50%,rgba(255,255,255,.15) 75%,transparent 75%,transparent)}.media,.media-body{overflow:hidden;zoom:1}.media,.media .media{margin-top:15px}.media:first-child{margin-top:0}.media-object{display:block}.media-heading{margin:0 0 5px}.media>.pull-left{margin-right:10px}.media>.pull-right{margin-left:10px}.media-list{padding-left:0;list-style:none}.list-group{margin-bottom:20px;padding-left:0}.list-group-item{position:relative;display:block;padding:10px 15px;margin-bottom:-1px;background-color:#fff;border:1px solid #ddd}.list-group-item:first-child{border-top-right-radius:4px;border-top-left-radius:4px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:4px;border-bottom-left-radius:4px}.list-group-item>.badge{float:right}.list-group-item>.badge+.badge{margin-right:5px}a.list-group-item{color:#555}a.list-group-item .list-group-item-heading{color:#333}a.list-group-item:hover,a.list-group-item:focus{text-decoration:none;background-color:#f5f5f5}a.list-group-item.active,a.list-group-item.active:hover,a.list-group-item.active:focus{z-index:2;color:#fff;background-color:#428bca;border-color:#428bca}a.list-group-item.active .list-group-item-heading,a.list-group-item.active:hover .list-group-item-heading,a.list-group-item.active:focus .list-group-item-heading{color:inherit}a.list-group-item.active .list-group-item-text,a.list-group-item.active:hover .list-group-item-text,a.list-group-item.active:focus .list-group-item-text{color:#e1edf7}.list-group-item-success{color:#3c763d;background-color:#dff0d8}a.list-group-item-success{color:#3c763d}a.list-group-item-success .list-group-item-heading{color:inherit}a.list-group-item-success:hover,a.list-group-item-success:focus{color:#3c763d;background-color:#d0e9c6}a.list-group-item-success.active,a.list-group-item-success.active:hover,a.list-group-item-success.active:focus{color:#fff;background-color:#3c763d;border-color:#3c763d}.list-group-item-info{color:#31708f;background-color:#d9edf7}a.list-group-item-info{color:#31708f}a.list-group-item-info .list-group-item-heading{color:inherit}a.list-group-item-info:hover,a.list-group-item-info:focus{color:#31708f;background-color:#c4e3f3}a.list-group-item-info.active,a.list-group-item-info.active:hover,a.list-group-item-info.active:focus{color:#fff;background-color:#31708f;border-color:#31708f}.list-group-item-warning{color:#8a6d3b;background-color:#fcf8e3}a.list-group-item-warning{color:#8a6d3b}a.list-group-item-warning .list-group-item-heading{color:inherit}a.list-group-item-warning:hover,a.list-group-item-warning:focus{color:#8a6d3b;background-color:#faf2cc}a.list-group-item-warning.active,a.list-group-item-warning.active:hover,a.list-group-item-warning.active:focus{color:#fff;background-color:#8a6d3b;border-color:#8a6d3b}.list-group-item-danger{color:#a94442;background-color:#f2dede}a.list-group-item-danger{color:#a94442}a.list-group-item-danger .list-group-item-heading{color:inherit}a.list-group-item-danger:hover,a.list-group-item-danger:focus{color:#a94442;background-color:#ebcccc}a.list-group-item-danger.active,a.list-group-item-danger.active:hover,a.list-group-item-danger.active:focus{color:#fff;background-color:#a94442;border-color:#a94442}.list-group-item-heading{margin-top:0;margin-bottom:5px}.list-group-item-text{margin-bottom:0;line-height:1.3}.panel{margin-bottom:20px;background-color:#fff;border:1px solid transparent;border-radius:4px;-webkit-box-shadow:0 1px 1px rgba(0,0,0,.05);box-shadow:0 1px 1px rgba(0,0,0,.05)}.panel-body{padding:15px}.panel-heading{padding:10px 15px;border-bottom:1px solid transparent;border-top-right-radius:3px;border-top-left-radius:3px}.panel-heading>.dropdown .dropdown-toggle{color:inherit}.panel-title{margin-top:0;margin-bottom:0;font-size:16px;color:inherit}.panel-title>a{color:inherit}.panel-footer{padding:10px 15px;background-color:#f5f5f5;border-top:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.list-group{margin-bottom:0}.panel>.list-group .list-group-item{border-width:1px 0;border-radius:0}.panel>.list-group:first-child .list-group-item:first-child{border-top:0;border-top-right-radius:3px;border-top-left-radius:3px}.panel>.list-group:last-child .list-group-item:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel-heading+.list-group .list-group-item:first-child{border-top-width:0}.panel>.table,.panel>.table-responsive>.table{margin-bottom:0}.panel>.table:first-child,.panel>.table-responsive:first-child>.table:first-child{border-top-right-radius:3px;border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:first-child,.panel>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:first-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:first-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:first-child{border-top-left-radius:3px}.panel>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child td:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child td:last-child,.panel>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>thead:first-child>tr:first-child th:last-child,.panel>.table:first-child>tbody:first-child>tr:first-child th:last-child,.panel>.table-responsive:first-child>.table:first-child>tbody:first-child>tr:first-child th:last-child{border-top-right-radius:3px}.panel>.table:last-child,.panel>.table-responsive:last-child>.table:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:first-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:first-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:first-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:first-child{border-bottom-left-radius:3px}.panel>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child td:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child td:last-child,.panel>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tbody:last-child>tr:last-child th:last-child,.panel>.table:last-child>tfoot:last-child>tr:last-child th:last-child,.panel>.table-responsive:last-child>.table:last-child>tfoot:last-child>tr:last-child th:last-child{border-bottom-right-radius:3px}.panel>.panel-body+.table,.panel>.panel-body+.table-responsive{border-top:1px solid #ddd}.panel>.table>tbody:first-child>tr:first-child th,.panel>.table>tbody:first-child>tr:first-child td{border-top:0}.panel>.table-bordered,.panel>.table-responsive>.table-bordered{border:0}.panel>.table-bordered>thead>tr>th:first-child,.panel>.table-responsive>.table-bordered>thead>tr>th:first-child,.panel>.table-bordered>tbody>tr>th:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:first-child,.panel>.table-bordered>tfoot>tr>th:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:first-child,.panel>.table-bordered>thead>tr>td:first-child,.panel>.table-responsive>.table-bordered>thead>tr>td:first-child,.panel>.table-bordered>tbody>tr>td:first-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:first-child,.panel>.table-bordered>tfoot>tr>td:first-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:first-child{border-left:0}.panel>.table-bordered>thead>tr>th:last-child,.panel>.table-responsive>.table-bordered>thead>tr>th:last-child,.panel>.table-bordered>tbody>tr>th:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>th:last-child,.panel>.table-bordered>tfoot>tr>th:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>th:last-child,.panel>.table-bordered>thead>tr>td:last-child,.panel>.table-responsive>.table-bordered>thead>tr>td:last-child,.panel>.table-bordered>tbody>tr>td:last-child,.panel>.table-responsive>.table-bordered>tbody>tr>td:last-child,.panel>.table-bordered>tfoot>tr>td:last-child,.panel>.table-responsive>.table-bordered>tfoot>tr>td:last-child{border-right:0}.panel>.table-bordered>thead>tr:first-child>td,.panel>.table-responsive>.table-bordered>thead>tr:first-child>td,.panel>.table-bordered>tbody>tr:first-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>td,.panel>.table-bordered>thead>tr:first-child>th,.panel>.table-responsive>.table-bordered>thead>tr:first-child>th,.panel>.table-bordered>tbody>tr:first-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:first-child>th{border-bottom:0}.panel>.table-bordered>tbody>tr:last-child>td,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>td,.panel>.table-bordered>tfoot>tr:last-child>td,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>td,.panel>.table-bordered>tbody>tr:last-child>th,.panel>.table-responsive>.table-bordered>tbody>tr:last-child>th,.panel>.table-bordered>tfoot>tr:last-child>th,.panel>.table-responsive>.table-bordered>tfoot>tr:last-child>th{border-bottom:0}.panel>.table-responsive{border:0;margin-bottom:0}.panel-group{margin-bottom:20px}.panel-group .panel{margin-bottom:0;border-radius:4px;overflow:hidden}.panel-group .panel+.panel{margin-top:5px}.panel-group .panel-heading{border-bottom:0}.panel-group .panel-heading+.panel-collapse .panel-body{border-top:1px solid #ddd}.panel-group .panel-footer{border-top:0}.panel-group .panel-footer+.panel-collapse .panel-body{border-bottom:1px solid #ddd}.panel-default{border-color:#ddd}.panel-default>.panel-heading{color:#333;background-color:#f5f5f5;border-color:#ddd}.panel-default>.panel-heading+.panel-collapse .panel-body{border-top-color:#ddd}.panel-default>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ddd}.panel-primary{border-color:#428bca}.panel-primary>.panel-heading{color:#fff;background-color:#428bca;border-color:#428bca}.panel-primary>.panel-heading+.panel-collapse .panel-body{border-top-color:#428bca}.panel-primary>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#428bca}.panel-success{border-color:#d6e9c6}.panel-success>.panel-heading{color:#3c763d;background-color:#dff0d8;border-color:#d6e9c6}.panel-success>.panel-heading+.panel-collapse .panel-body{border-top-color:#d6e9c6}.panel-success>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#d6e9c6}.panel-info{border-color:#bce8f1}.panel-info>.panel-heading{color:#31708f;background-color:#d9edf7;border-color:#bce8f1}.panel-info>.panel-heading+.panel-collapse .panel-body{border-top-color:#bce8f1}.panel-info>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#bce8f1}.panel-warning{border-color:#faebcc}.panel-warning>.panel-heading{color:#8a6d3b;background-color:#fcf8e3;border-color:#faebcc}.panel-warning>.panel-heading+.panel-collapse .panel-body{border-top-color:#faebcc}.panel-warning>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#faebcc}.panel-danger{border-color:#ebccd1}.panel-danger>.panel-heading{color:#a94442;background-color:#f2dede;border-color:#ebccd1}.panel-danger>.panel-heading+.panel-collapse .panel-body{border-top-color:#ebccd1}.panel-danger>.panel-footer+.panel-collapse .panel-body{border-bottom-color:#ebccd1}.well{min-height:20px;padding:19px;margin-bottom:20px;background-color:#f5f5f5;border:1px solid #e3e3e3;border-radius:4px;-webkit-box-shadow:inset 0 1px 1px rgba(0,0,0,.05);box-shadow:inset 0 1px 1px rgba(0,0,0,.05)}.well blockquote{border-color:#ddd;border-color:rgba(0,0,0,.15)}.well-lg{padding:24px;border-radius:6px}.well-sm{padding:9px;border-radius:3px}.close{float:right;font-size:21px;font-weight:700;line-height:1;color:#000;text-shadow:0 1px 0 #fff;opacity:.2;filter:alpha(opacity=20)}.close:hover,.close:focus{color:#000;text-decoration:none;cursor:pointer;opacity:.5;filter:alpha(opacity=50)}button.close{padding:0;cursor:pointer;background:0 0;border:0;-webkit-appearance:none}.modal-open{overflow:hidden}.modal{display:none;overflow:auto;overflow-y:scroll;position:fixed;top:0;right:0;bottom:0;left:0;z-index:1050;-webkit-overflow-scrolling:touch;outline:0}.modal.fade .modal-dialog{-webkit-transform:translate(0,-25%);-ms-transform:translate(0,-25%);transform:translate(0,-25%);-webkit-transition:-webkit-transform .3s ease-out;-moz-transition:-moz-transform .3s ease-out;-o-transition:-o-transform .3s ease-out;transition:transform .3s ease-out}.modal.in .modal-dialog{-webkit-transform:translate(0,0);-ms-transform:translate(0,0);transform:translate(0,0)}.modal-dialog{position:relative;width:auto;margin:10px}.modal-content{position:relative;background-color:#fff;border:1px solid #999;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 3px 9px rgba(0,0,0,.5);box-shadow:0 3px 9px rgba(0,0,0,.5);background-clip:padding-box;outline:0}.modal-backdrop{position:fixed;top:0;right:0;bottom:0;left:0;z-index:1040;background-color:#000}.modal-backdrop.fade{opacity:0;filter:alpha(opacity=0)}.modal-backdrop.in{opacity:.5;filter:alpha(opacity=50)}.modal-header{padding:15px;border-bottom:1px solid #e5e5e5;min-height:16.42857143px}.modal-header .close{margin-top:-2px}.modal-title{margin:0;line-height:1.42857143}.modal-body{position:relative;padding:20px}.modal-footer{margin-top:15px;padding:19px 20px 20px;text-align:right;border-top:1px solid #e5e5e5}.modal-footer .btn+.btn{margin-left:5px;margin-bottom:0}.modal-footer .btn-group .btn+.btn{margin-left:-1px}.modal-footer .btn-block+.btn-block{margin-left:0}@media (min-width:768px){.modal-dialog{width:600px;margin:30px auto}.modal-content{-webkit-box-shadow:0 5px 15px rgba(0,0,0,.5);box-shadow:0 5px 15px rgba(0,0,0,.5)}.modal-sm{width:300px}}@media (min-width:992px){.modal-lg{width:900px}}.tooltip{position:absolute;z-index:1030;display:block;visibility:visible;font-size:12px;line-height:1.4;opacity:0;filter:alpha(opacity=0)}.tooltip.in{opacity:.9;filter:alpha(opacity=90)}.tooltip.top{margin-top:-3px;padding:5px 0}.tooltip.right{margin-left:3px;padding:0 5px}.tooltip.bottom{margin-top:3px;padding:5px 0}.tooltip.left{margin-left:-3px;padding:0 5px}.tooltip-inner{max-width:200px;padding:3px 8px;color:#fff;text-align:center;text-decoration:none;background-color:#000;border-radius:4px}.tooltip-arrow{position:absolute;width:0;height:0;border-color:transparent;border-style:solid}.tooltip.top .tooltip-arrow{bottom:0;left:50%;margin-left:-5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-left .tooltip-arrow{bottom:0;left:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.top-right .tooltip-arrow{bottom:0;right:5px;border-width:5px 5px 0;border-top-color:#000}.tooltip.right .tooltip-arrow{top:50%;left:0;margin-top:-5px;border-width:5px 5px 5px 0;border-right-color:#000}.tooltip.left .tooltip-arrow{top:50%;right:0;margin-top:-5px;border-width:5px 0 5px 5px;border-left-color:#000}.tooltip.bottom .tooltip-arrow{top:0;left:50%;margin-left:-5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-left .tooltip-arrow{top:0;left:5px;border-width:0 5px 5px;border-bottom-color:#000}.tooltip.bottom-right .tooltip-arrow{top:0;right:5px;border-width:0 5px 5px;border-bottom-color:#000}.popover{position:absolute;top:0;left:0;z-index:1010;display:none;max-width:276px;padding:1px;text-align:left;background-color:#fff;background-clip:padding-box;border:1px solid #ccc;border:1px solid rgba(0,0,0,.2);border-radius:6px;-webkit-box-shadow:0 5px 10px rgba(0,0,0,.2);box-shadow:0 5px 10px rgba(0,0,0,.2);white-space:normal}.popover.top{margin-top:-10px}.popover.right{margin-left:10px}.popover.bottom{margin-top:10px}.popover.left{margin-left:-10px}.popover-title{margin:0;padding:8px 14px;font-size:14px;font-weight:400;line-height:18px;background-color:#f7f7f7;border-bottom:1px solid #ebebeb;border-radius:5px 5px 0 0}.popover-content{padding:9px 14px}.popover>.arrow,.popover>.arrow:after{position:absolute;display:block;width:0;height:0;border-color:transparent;border-style:solid}.popover>.arrow{border-width:11px}.popover>.arrow:after{border-width:10px;content:""}.popover.top>.arrow{left:50%;margin-left:-11px;border-bottom-width:0;border-top-color:#999;border-top-color:rgba(0,0,0,.25);bottom:-11px}.popover.top>.arrow:after{content:" ";bottom:1px;margin-left:-10px;border-bottom-width:0;border-top-color:#fff}.popover.right>.arrow{top:50%;left:-11px;margin-top:-11px;border-left-width:0;border-right-color:#999;border-right-color:rgba(0,0,0,.25)}.popover.right>.arrow:after{content:" ";left:1px;bottom:-10px;border-left-width:0;border-right-color:#fff}.popover.bottom>.arrow{left:50%;margin-left:-11px;border-top-width:0;border-bottom-color:#999;border-bottom-color:rgba(0,0,0,.25);top:-11px}.popover.bottom>.arrow:after{content:" ";top:1px;margin-left:-10px;border-top-width:0;border-bottom-color:#fff}.popover.left>.arrow{top:50%;right:-11px;margin-top:-11px;border-right-width:0;border-left-color:#999;border-left-color:rgba(0,0,0,.25)}.popover.left>.arrow:after{content:" ";right:1px;border-right-width:0;border-left-color:#fff;bottom:-10px}.carousel{position:relative}.carousel-inner{position:relative;overflow:hidden;width:100%}.carousel-inner>.item{display:none;position:relative;-webkit-transition:.6s ease-in-out left;transition:.6s ease-in-out left}.carousel-inner>.item>img,.carousel-inner>.item>a>img{line-height:1}.carousel-inner>.active,.carousel-inner>.next,.carousel-inner>.prev{display:block}.carousel-inner>.active{left:0}.carousel-inner>.next,.carousel-inner>.prev{position:absolute;top:0;width:100%}.carousel-inner>.next{left:100%}.carousel-inner>.prev{left:-100%}.carousel-inner>.next.left,.carousel-inner>.prev.right{left:0}.carousel-inner>.active.left{left:-100%}.carousel-inner>.active.right{left:100%}.carousel-control{position:absolute;top:0;left:0;bottom:0;width:15%;opacity:.5;filter:alpha(opacity=50);font-size:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-control.left{background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.5) 0),color-stop(rgba(0,0,0,.0001) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.5) 0,rgba(0,0,0,.0001) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#80000000', endColorstr='#00000000', GradientType=1)}.carousel-control.right{left:auto;right:0;background-image:-webkit-linear-gradient(left,color-stop(rgba(0,0,0,.0001) 0),color-stop(rgba(0,0,0,.5) 100%));background-image:linear-gradient(to right,rgba(0,0,0,.0001) 0,rgba(0,0,0,.5) 100%);background-repeat:repeat-x;filter:progid:DXImageTransform.Microsoft.gradient(startColorstr='#00000000', endColorstr='#80000000', GradientType=1)}.carousel-control:hover,.carousel-control:focus{outline:0;color:#fff;text-decoration:none;opacity:.9;filter:alpha(opacity=90)}.carousel-control .icon-prev,.carousel-control .icon-next,.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right{position:absolute;top:50%;z-index:5;display:inline-block}.carousel-control .icon-prev,.carousel-control .glyphicon-chevron-left{left:50%}.carousel-control .icon-next,.carousel-control .glyphicon-chevron-right{right:50%}.carousel-control .icon-prev,.carousel-control .icon-next{width:20px;height:20px;margin-top:-10px;margin-left:-10px;font-family:serif}.carousel-control .icon-prev:before{content:'\2039'}.carousel-control .icon-next:before{content:'\203a'}.carousel-indicators{position:absolute;bottom:10px;left:50%;z-index:15;width:60%;margin-left:-30%;padding-left:0;list-style:none;text-align:center}.carousel-indicators li{display:inline-block;width:10px;height:10px;margin:1px;text-indent:-999px;border:1px solid #fff;border-radius:10px;cursor:pointer;background-color:#000 \9;background-color:rgba(0,0,0,0)}.carousel-indicators .active{margin:0;width:12px;height:12px;background-color:#fff}.carousel-caption{position:absolute;left:15%;right:15%;bottom:20px;z-index:10;padding-top:20px;padding-bottom:20px;color:#fff;text-align:center;text-shadow:0 1px 2px rgba(0,0,0,.6)}.carousel-caption .btn{text-shadow:none}@media screen and (min-width:768px){.carousel-control .glyphicon-chevron-left,.carousel-control .glyphicon-chevron-right,.carousel-control .icon-prev,.carousel-control .icon-next{width:30px;height:30px;margin-top:-15px;margin-left:-15px;font-size:30px}.carousel-caption{left:20%;right:20%;padding-bottom:30px}.carousel-indicators{bottom:20px}}.clearfix:before,.clearfix:after,.container:before,.container:after,.container-fluid:before,.container-fluid:after,.row:before,.row:after,.form-horizontal .form-group:before,.form-horizontal .form-group:after,.btn-toolbar:before,.btn-toolbar:after,.btn-group-vertical>.btn-group:before,.btn-group-vertical>.btn-group:after,.nav:before,.nav:after,.navbar:before,.navbar:after,.navbar-header:before,.navbar-header:after,.navbar-collapse:before,.navbar-collapse:after,.pager:before,.pager:after,.panel-body:before,.panel-body:after,.modal-footer:before,.modal-footer:after{content:" ";display:table}.clearfix:after,.container:after,.container-fluid:after,.row:after,.form-horizontal .form-group:after,.btn-toolbar:after,.btn-group-vertical>.btn-group:after,.nav:after,.navbar:after,.navbar-header:after,.navbar-collapse:after,.pager:after,.panel-body:after,.modal-footer:after{clear:both}.center-block{display:block;margin-left:auto;margin-right:auto}.pull-right{float:right!important}.pull-left{float:left!important}.hide{display:none!important}.show{display:block!important}.invisible{visibility:hidden}.text-hide{font:0/0 a;color:transparent;text-shadow:none;background-color:transparent;border:0}.hidden{display:none!important;visibility:hidden!important}.affix{position:fixed}@-ms-viewport{width:device-width}.visible-xs,.visible-sm,.visible-md,.visible-lg{display:none!important}@media (max-width:767px){.visible-xs{display:block!important}table.visible-xs{display:table}tr.visible-xs{display:table-row!important}th.visible-xs,td.visible-xs{display:table-cell!important}}@media (min-width:768px) and (max-width:991px){.visible-sm{display:block!important}table.visible-sm{display:table}tr.visible-sm{display:table-row!important}th.visible-sm,td.visible-sm{display:table-cell!important}}@media (min-width:992px) and (max-width:1199px){.visible-md{display:block!important}table.visible-md{display:table}tr.visible-md{display:table-row!important}th.visible-md,td.visible-md{display:table-cell!important}}@media (min-width:1200px){.visible-lg{display:block!important}table.visible-lg{display:table}tr.visible-lg{display:table-row!important}th.visible-lg,td.visible-lg{display:table-cell!important}}@media (max-width:767px){.hidden-xs{display:none!important}}@media (min-width:768px) and (max-width:991px){.hidden-sm{display:none!important}}@media (min-width:992px) and (max-width:1199px){.hidden-md{display:none!important}}@media (min-width:1200px){.hidden-lg{display:none!important}}.visible-print{display:none!important}@media print{.visible-print{display:block!important}table.visible-print{display:table}tr.visible-print{display:table-row!important}th.visible-print,td.visible-print{display:table-cell!important}}@media print{.hidden-print{display:none!important}} \ No newline at end of file diff --git a/src/main/resources/help_html/css/styles.css b/src/main/resources/help_html/css/styles.css new file mode 100644 index 000000000000..defdc3a2e918 --- /dev/null +++ b/src/main/resources/help_html/css/styles.css @@ -0,0 +1,69 @@ +/* +A Bootstrap 3.1 affix sidebar template +from http://bootply.com + +This CSS code should follow the 'bootstrap.css' +in your HTML file. + +license: MIT +author: bootply.com +*/ + +body { + padding-top:50px; +} + +#masthead { + min-height:250px; +} + +#masthead h1 { + font-size: 30px; + line-height: 1; + padding-top:20px; +} + +#masthead .well { + margin-top:8%; +} + +@media screen and (min-width: 768px) { + #masthead h1 { + font-size: 50px; + } +} + +.navbar-bright { + background-color:#111155; + color:#fff; +} + +.affix-top,.affix{ + position: static; +} + +@media (min-width: 979px) { + #sidebar.affix-top { + position: static; + width:228px; + } + + #sidebar.affix { + position: fixed; + top:70px; + width:228px; + } +} + +#sidebar li.active { + border:0 #eee solid; + border-right-width:5px; +} + + + +.marketing .pagehead h1{font-size:30px}.marketing .pagehead p{margin-top:4px;margin-bottom:0;font-size:14px;color:#767676}.marketing .pagehead ul.actions{margin-top:10px}.marketing h2 .secure{float:right;padding:1px 0;font-size:11px;font-weight:bold;color:#6cc644;text-transform:uppercase}.marketing .questions p{font-size:14px}.marketing-header{margin-bottom:40px}.marketing-header h1{margin-top:0;margin-bottom:0;font-size:42px;font-weight:300}.marketing-header .lead{max-width:750px;margin:10px auto 0;color:#767676}.marketing-header .btn{padding:12px 20px;margin-top:15px;font-size:18px;font-weight:normal;border-radius:6px}.marketing-section{position:relative;padding-top:80px;padding-bottom:80px;font-size:16px;line-height:1.5;text-align:center;border-bottom:1px solid #e5e5e5}.marketing-section::before{display:table;content:""}.marketing-section::after{display:table;clear:both;content:""}.marketing-section h3{font-size:21px;font-weight:normal}.marketing-section-stripe{background-color:#f9f9f9}.marketing-hero-octicon{position:relative;width:100px;height:100px;margin:0 auto 15px;text-align:center;border:solid 1px #e5e5e5;border-radius:50px}.marketing-hero-octicon .octicon{margin-top:22px;color:#4078c0}.marketing-hero-octicon .octicon-checklist{position:relative;right:-3px}.marketing-grid{font-size:14px}.marketing-grid .column{padding:20px 25px 40px}.marketing-grid p{max-width:90%;margin:0 auto;color:#5a5a5a}.marketing-grid .octicon{color:#4078c0}.hanging-list li,.hanging-icon-list li{margin:10px 0;font-size:14px}.hanging-list li{margin-left:12px;list-style-position:inside}.hanging-icon-list li{padding-left:25px;list-style-type:none}.hanging-icon-list .octicon{float:left;margin-left:-20px;color:#767676}.hanging-icon-list .octicon-check{color:#6cc644}.hanging-icon-list .octicon-x{color:#bd2c00}.logos-page h3{font-size:18px}.logos-download{position:relative;display:block;float:left;width:32%;height:290px;padding-top:20px;margin-bottom:30px;text-align:center;border:1px solid #ddd;border-radius:6px}.logos-download+.logos-download{margin-left:2%}.logos-download .gh-logo{margin-top:70px}.logos-download .gh-octocat{margin-top:10px}.logos-download-link{position:absolute;right:0;bottom:0;left:0;display:block;padding:15px 20px;font-size:16px;font-weight:bold;background-color:#f5f5f5;border-top:1px solid #ddd;border-radius:0 0 5px 5px}.logos-download-link .octicon{vertical-align:2px}.logos-download:hover{text-decoration:none}.logos-download:hover .logos-download-link{background-color:#eee}.nonprofit-head{position:relative;padding:100px 0 120px;overflow:hidden;text-align:center;border-bottom:1px solid #eee}.nonprofit-head .title{display:inline-block;margin-bottom:20px;font-size:30px;font-weight:300;color:#767676;border-bottom:1px solid #ccc}.nonprofit-head .title .octicon{color:#333}.nonprofit-head .logo{vertical-align:middle}.nonprofit h1{position:relative;font-size:28px;font-weight:300;line-height:1.5em}.nonprofit h2{font-weight:normal}.heart{position:absolute;top:40%;left:50%;width:12px;height:12px;margin-left:400px;background:#83d6c0;box-shadow:140px 30px 0 #efa, 120px -120px 0 #aded84, 220px -60px 0 #ded, 30px 240px 0 #ada, 60px -60px 0 #d76666, 60px -30px 0 #ff846f, 60px 0 0 #f9a7a7, 60px 30px 0 #ffc8c8, 60px 60px 0 #ffd8d8, 30px 60px 0 #baf2ca, 30px 30px 0 #98eaac, 30px 0 0 #80d896, 30px -30px 0 #6dd085, 30px -60px 0 #55be6f, 0 -60px 0 #4cc2a7, 0 -30px 0 #73d3b9, 0 30px 0 #93e3cd, 0 60px 0 #adf9e4, -30px 60px 0 #ffe1b9, -30px 30px 0 #ffd194, -30px 0 0 #ffc86f, -60px 0 0 #fd9ff0, -60px 30px 0 #ffbaf7, -60px 60px 0 #fccdf7, -180px 60px 0 #9df;-webkit-transform:rotate(45deg);transform:rotate(45deg)}.heart.left{margin-left:-400px;-webkit-transform:rotate(-45deg),scaleX(-1);transform:rotate(-45deg),scaleX(-1)}.octo-earth{position:absolute;bottom:-150px;left:50%;margin-left:-120px;-webkit-animation:rotate 20s infinite linear;animation:rotate 20s infinite linear}@-webkit-keyframes rotate{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(-360deg);transform:rotate(-360deg)}}@keyframes rotate{0%{-webkit-transform:rotate(0);transform:rotate(0)}100%{-webkit-transform:rotate(-360deg);transform:rotate(-360deg)}}.nonprofit-steps{margin-left:30px;font-size:20px;font-weight:300}.nonprofit-steps li{margin-bottom:10px}.nonprofit-steps ul{margin:15px 0 0 20px;list-style:square}.nonprofit-section{padding:50px 0;background:#f5f5f5}.nonprofit-section h1{text-align:center}.nonprofit-section .dialog{width:640px;padding:30px;margin:30px auto 0;background:#fff}.nonprofit-section .dialog p:last-child{margin-bottom:0}.dialog.edu-callout{padding:25px;border:5px solid #aec}.dialog.edu-callout .octicon{float:left;padding-top:10px;padding-bottom:10px;margin-right:15px;color:#418f65}.integrations-hero-octicon.marketing-hero-octicon{width:75px;height:75px;border-width:5px}.integrations-hero-octicon.marketing-hero-octicon .octicon{margin-top:15px}.marketing-blue-octicon{color:#34acbf;border-color:#34acbf}.marketing-blue-octicon .octicon{color:#34acbf}.marketing-turquoise-octicon{color:#75bbb6;border-color:#75bbb6}.marketing-turquoise-octicon .octicon{color:#75bbb6}.marketing-purple-octicon{color:#b086b7;border-color:#b086b7}.marketing-purple-octicon .octicon{color:#b086b7}.marketing-graphic{position:relative}.marketing-graphic .in-page-link{position:absolute;top:0;bottom:0;display:block;width:20%;content:" "}.marketing-graphic .in-page-link.code,.marketing-graphic .in-page-link.build{left:20%}.marketing-graphic .in-page-link.collaborate{left:40%}.marketing-graphic .in-page-link.ship,.marketing-graphic .in-page-link.deploy{left:60%}.pbl-intgrs .site-footer{margin-top:-99px;border-top:1px solid #e5e5e5}.pbl-intgrs-intro{padding:60px 0;background:#ecf9f8;background-image:-webkit-linear-gradient(45deg, #ecf9f8 0%, #fbf4fc 100%);background-image:linear-gradient(45deg, #ecf9f8 0%, #fbf4fc 100%)}.pbl-intgrs-intro .marketing-header{margin-bottom:0}.pbl-intgrs-intro h1{margin-bottom:20px;font-size:48px;font-weight:200}.pbl-intgrs-intro .lead{max-width:540px;font-size:18px;font-weight:300;color:rgba(87,87,87,0.7)}.pbl-intgrs-intro .marketing-graphic{margin:52px 0 0}.pbl-intgrs-intro .integrations-hero-octicon.marketing-hero-octicon{width:75px;height:75px;margin:-30px auto 30px;border-width:5px}.pbl-intgrs-intro .integrations-hero-octicon.marketing-hero-octicon .octicon{font-size:34px;line-height:66px}.pbl-intgrs-list .lead{color:#767676}.pbl-intgrs-list .lead.narrow{max-width:520px}.pbl-intgrs-outro{padding-top:118px;padding-bottom:218px;background:#ecf9f8;background-image:-webkit-linear-gradient(45deg, #ecf9f8 0%, #fbf4fc 100%);background-image:linear-gradient(45deg, #ecf9f8 0%, #fbf4fc 100%)}.pbl-intgrs-outro .lead{width:550px;color:rgba(87,87,87,0.7)}.btn.outro-button{padding:14px 65px;font-size:20px;font-weight:200;color:#fff;text-decoration:none;text-shadow:none;background:#75bbb6;border:0;-webkit-transition:opacity ease 0.1s;transition:opacity ease 0.1s}.btn.outro-button:hover{opacity:0.8}.pbl-intgrs-logo-container{min-height:80px;margin-bottom:20px}.intgrs-dir .marketing-graphic{padding-right:0;margin:0}.intgrs-dir .site-footer{margin-top:40px}.intgrs-dir-section h2{margin-top:0;margin-bottom:20px;font-size:26px;font-weight:300}.intgrs-dir-intro{padding:40px 0;margin:0;text-align:left;background-image:-webkit-linear-gradient(200deg, #48227d 0%, #2f569c 100%);background-image:linear-gradient(-110deg, #48227d 0%, #2f569c 100%);border-bottom:0}.pagehead+.intgrs-dir-intro{margin-top:-20px}.intgrs-dir-intro.ultra-slim{padding:20px 0}.intgrs-dir-intro .directory-header-back{margin-top:10px;font-size:18px;color:#fff}.intgrs-dir-intro .directory-header-back:hover{color:#d7def1;text-decoration:none}.intgrs-dir-intro .directory-header-back .octicon{vertical-align:middle}.intgrs-dir-intro .directory-header-back .header-link{color:#96dad9}.intgrs-dir-intro .column{padding-right:40px;padding-left:0}.intgrs-dir-intro .directory-tag-line{margin-bottom:0;font-size:28px;font-weight:400;color:#fff}.intgrs-dir-intro .lead{margin-top:10px;margin-bottom:6px;font-size:18px;font-weight:normal;color:#d7def1}.intgrs-lstng-search{display:inline-block;width:25%;margin-left:20px}.intgrs-lstng-search .subnav-search-input{width:100%}.intgrs-lstng-categories-container{display:inline-block;float:left;width:20%}.intgrs-lstng-categories-container .intgrs-lstng-categories{top:0}.intgrs-lstng-categories-container .intgrs-categories-heading{font-size:16px;font-weight:bold}.intgrs-lstng-container{display:inline-block;width:80%;text-align:left}.intgrs-lstng-item{position:relative;display:-webkit-inline-box;display:inline-flex;width:31.4%;font-size:14px;border:1px solid #ededed;border-radius:4px;-webkit-transition:border-color 0.15s ease 0s, -webkit-transform 0.15s ease 0s, box-shadow 0.15s ease 0s, color 0.15s ease 0s;transition:border-color 0.15s ease 0s, transform 0.15s ease 0s, box-shadow 0.15s ease 0s, color 0.15s ease 0s}.intgrs-lstng-item:hover{border-color:#51a7e8;box-shadow:0 0 5px rgba(81,167,232,0.5);-webkit-transform:scale(1.05);transform:scale(1.05)}.intgrs-lstng-item .intgrs-lstng-logo{display:block;margin:0 auto 10px}.intgrs-lstng-item .draft-tag{position:absolute;top:-1px;left:10px}.intgrs-lstng-item-link{display:block;width:100%;height:181px;padding-top:20px}.intgrs-lstng-item-link:hover{text-decoration:none}.intgrs-lstng-item-link:hover .intgrs-lstng-item-header{color:#4078c0}.intgrs-lstng-item-header{margin:15px 10px 0;font-size:14px;font-weight:bold;color:#333}.intgrs-lstng-item-description{position:relative;height:2.8em;padding:0 10px;margin-top:5px;overflow:hidden;font-size:13px;color:#767676}.intgrs-lstng-item-description::after{position:absolute;right:0;bottom:0;padding:0 15px 0 20px;color:transparent;content:"\00a0";background-image:-webkit-linear-gradient(left, rgba(255,255,255,0), #fff 80%);background-image:linear-gradient(to right, rgba(255,255,255,0), #fff 80%)}.intgr-header{padding-bottom:30px;margin-top:30px;margin-bottom:30px;font-size:16px;border-bottom:1px solid #eee}.intgr-header h2{font-size:30px;font-weight:400}.intgr-header p{width:530px;margin-top:5px;color:#767676}.intgr-header h2,.intgr-header p{margin-bottom:0;margin-left:120px}.intgr-install-callout{padding:20px;margin-top:-135px;margin-bottom:20px;background-color:#fff;border:1px solid #eee;border-radius:4px}.intgr-install-callout .btn{padding:12px 20px;font-size:18px}.intgr-install-callout p{margin-top:15px;margin-bottom:0;line-height:1.6}.intgr-install-callout .boxed-group-list .tooltipped{cursor:pointer}.intgr-install-callout .boxed-group-list .tooltipped.not-allowed{cursor:not-allowed}.intgr-install-callout .boxed-group-list .octicon{margin-top:3px;margin-left:4px}.intgr-install-callout .boxed-group-list.is-parent{margin-top:20px;margin-bottom:-20px}.intgr-install-callout .boxed-group-list li{padding:5px 20px;margin-right:-20px;margin-left:-20px}.intgr-install-callout .boxed-group-list li.muted{background-color:#fafafa}.intgr-install-callout .boxed-group-list li:first-child{border-top:1px solid #ddd}.intgr-install-callout .avatar-paragraph{position:relative;padding-left:50px}.intgr-install-callout .dropdown-menu{top:37px;width:313px;margin-right:-1px}.intgr-install-callout .dropdown-menu .boxed-group-list li{margin-right:0;margin-left:0}.intgr-install-callout .dropdown-menu .boxed-group-list li:only-child{border-radius:3px}.intgr-install-callout .dropdown-menu .boxed-group-list li:first-child{border-radius:3px 3px 0 0}.intgr-install-callout .dropdown-menu .boxed-group-list li:last-child{border-radius:0 0 3px 3px}.intgr-install-callout .avatar-octicon-lockup{position:absolute;top:0;left:0}.intgr-install-callout .avatar-octicon-lockup .octicon{position:absolute;right:-6px;bottom:-6px;text-shadow:-2px -2px 0 #fff}.intgr-install-callout .avatar-octicon-lockup .octicon::after{position:absolute;top:6px;left:-1px;display:block;width:7px;height:2px;content:"";background-color:#fff;-webkit-transform:rotate(-45deg);transform:rotate(-45deg)}.intgr-info-list{padding:0 20px;color:#767676}.intgr-info-list .btn-outline{margin-bottom:5px}.intgr-info-list dd{margin-bottom:30px}.intgr-info-list li{list-style-type:none}.intgr-admin-link{position:relative;display:inline-block;height:25px;padding-left:23px;font-size:13px;vertical-align:middle;border:1px solid #ededed;border-radius:3px}.intgr-admin-link.draft-tag{padding-left:25px;border:0}.intgr-admin-link.draft-tag .octicon,.intgr-admin-link.draft-tag:hover .octicon{color:#fff}.intgr-admin-link.draft-tag:hover{text-decoration:none;background-color:#000}.intgr-admin-link:hover .octicon{color:#4078c0}.intgr-admin-link .octicon{position:absolute;top:3px;left:5px;color:#767676}.intgr-feat-header{position:relative;width:85%;padding:0 65px 10px;color:#d7def1}.intgr-feat-header .intgr-admin-link{border-color:rgba(215,222,241,0.6)}.intgr-feat-header .intgr-admin-link .octicon{color:#d7def1}.intgr-feat-header .intgr-admin-link:hover .octicon{color:#fff}.intgr-feat-header .marketing-hero-octicon{position:absolute;top:0;left:5px;width:50px;height:50px;border-width:3px}.intgr-feat-header .marketing-hero-octicon .octicon{margin-top:11px}.intgr-feat-header h2{margin:0;font-size:25px;line-height:50px;color:#fff}.intgr-feat-header p{max-width:580px;margin:0;font-size:18px}.integrations-breadcrumb{display:inline-block;font-weight:normal;color:#8296cc}.integrations-breadcrumb-link{line-height:0;color:#d7def1}.integrations-breadcrumb-link:hover{color:#fff;text-decoration:none}.integration-settings-callout{padding:20px 20px 20px 80px;margin-bottom:10px;text-align:left;background-color:#fff;border-color:#d8d8d8;box-shadow:none}.integration-settings-callout h3{margin:0}.integration-settings-callout p{width:450px;margin:0;font-size:12px;color:#767676}.integration-settings-callout .marketing-hero-octicon{float:left;width:40px;height:40px;margin:5px 0 0 -60px;border-width:2px}.integration-settings-callout .marketing-hero-octicon .octicon-code{width:17px;height:20px;margin-top:7px}.integration-settings-callout .browse-directory-btn{margin-left:20px}.integrations-callout-standalone .integration-settings-callout{margin-bottom:20px;border-color:#e5e5e5;border-radius:3px}.integrations-auth-wrapper{width:511px;margin:60px auto}.integrations-auth-header{font-size:20px;text-align:center}.integrations-permissions-group dt{font-size:18px;font-weight:normal}.integrations-permissions-group .integrations-permission{position:relative;padding-left:22px;margin-bottom:10px;list-style-type:none}.integrations-permissions-group .integrations-permission .octicon{position:absolute;top:1px;left:0;margin-right:10px}.integrations-install-target .select-menu{vertical-align:middle}.integrations-install-target input[type=radio]{margin-right:10px}.integrations-install-target .flash{background-color:transparent}.integrations-install-target .flash-error{background-color:transparent;border:0}.integrations-install-target .octicon-lock,.integrations-install-target .octicon-repo{margin-right:3px}.integrations-install-target .octicon-lock{color:#4c4a42}.integrations-install-target .private{background-color:#fff9ea}.integrations-install-target .navigation-focus.private{background-color:#4078c0}.integrations-install-target .navigation-focus .octicon-lock{color:inherit}.integration-description p.truncated-description{display:block;margin-bottom:0}.integration-description .full-description{display:none;margin-bottom:0}.integration-description.open .truncated-description{display:none}.integration-description.open .full-description{display:block}.integration-categories{margin-top:20px}.integration-landing .markdown-body p{margin-top:16px}.integration-landing .integration-description{font-size:18px;font-weight:300;color:#555}.integration-landing .body-type-next{font-size:16px;line-height:1.6;word-wrap:break-word}.integration-landing .item-author{padding-right:20px}.integration-landing .band{padding:40px 0}.integration-landing .band p:last-child{margin-bottom:0}.integration-settings-show .octicon-header .octicon{margin-right:10px;color:#999;vertical-align:middle}.integration-settings-show .listgroup-item{line-height:inherit}.integration-settings-show .listgroup .listgroup-padded .listgroup-item{min-height:40px;padding:15px}.integration-settings-show .listgroup-item-title{line-height:21px;color:#333}.integration-settings-show .subhead-danger{border-bottom:1px solid #fcdede}.integration-settings-show .subhead-danger .subhead-heading{font-weight:bold;color:#bd2c00}.integration-settings-show .subhead.subhead-nude{margin-bottom:4px;border-bottom-width:0 !important}.integration-settings-show .flex-block-container{display:-webkit-box;display:flex;flex-flow:row wrap;-webkit-box-pack:start;justify-content:flex-start;-webkit-box-align:start;align-items:flex-start}.integration-settings-show .flex-block-container .flex-block-item{width:233px;min-width:230px;min-height:89px;padding:15px;margin-bottom:15px;border:1px solid #ddd;border-radius:3px}.integration-settings-show .flex-block-container .flex-block-item:nth-child(3n+2){margin-right:15px;margin-left:15px}.integration-settings-show code.markdown-style{padding:0.2em;margin:0;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:100%;background-color:rgba(0,0,0,0.04);border-radius:3px}.integration-settings-show .fw-normal{font-weight:normal !important}.listgroup-item{line-height:inherit}.listgroup-item.disabled{background-color:#fafafa}.listgroup-item.disabled .listgroup-item-title{color:#333}.listgroup.listgroup-padded .listgroup-item{min-height:40px;padding:15px}.listgroup-danger{border-color:#fcdede}.listgroup-danger .listgroup-item{border-color:#fcdede}.integration-key-management-wrapper .integration-key-downloading{display:none}.integration-key-management-wrapper.downloading .integration-key-downloading{display:block}.integration-key-management-wrapper.downloading .integration-key-management{display:none}.integration-key-management-wrapper .blankslate{margin-bottom:30px}.integration-permissions-selector .select-menu-item-text .octicon{position:relative;top:-1px;margin-right:4px}.integration-permissions-container{color:#333}.integration-permissions-container .integration-permissions-info{display:inline-block;width:74%}.integration-permissions-container .listgroup-item-title{display:inline-block;width:25%;vertical-align:top}.integration-permissions-container .listgroup-item-body{display:inline-block;max-width:72%}.integration-permissions-container .send-events{display:block}.integration-permissions-container .send-events .hook-event{line-height:1.6;vertical-align:top}.integration-permissions-container .send-events .flash{background-color:transparent}.integration-permissions-container .send-events .flash-error{background-color:transparent;border:0}.nice-meta-list{display:block;padding:12px 0;list-style-type:none}.nice-meta-list dt{color:#999}.nice-meta-list dd{margin-bottom:12px}.list-group-avatar{margin-top:-2px;margin-right:4px;vertical-align:middle;border-radius:3px}.link-small{color:#767676;-webkit-transition:color 500ms ease;transition:color 500ms ease}.listgroup-item:hover .link-small{color:#4078c0}.bot-identifier{padding:1px 6px;font-size:12px;font-weight:normal;color:#767676;background-color:none;border:1px solid #d8d8d8;border-radius:3px}.features-next .lead strong{color:#444}.features-next .native-mobile-screens{padding-left:0;line-height:0;list-style-type:none;border-bottom:solid 1px #d9d9d9}.features-next .native-mobile-screens li{display:inline;margin-right:5px;margin-left:5px}.team-org-chart{width:470px;margin:30px auto}.team-org-chart .octicon{vertical-align:middle}.team-org-group{padding:10px;margin-bottom:13px;font-size:16px;text-align:center;background-color:#fff;border:solid 1px #ccc;border-radius:3px}.team-org-group strong{color:#333}.team-org-team{display:inline-block;width:147px;height:120px;vertical-align:top}.team-org-team+.team-org-team{margin-left:10px}.jersey-red{color:#bd2c00}.jersey-green{color:#6cc644}.jersey-orange{color:#c9510c}.team-org-members{margin-top:15px}.team-org-members .octicon{color:#aaa}.team-org-repos .octicon{margin:0 5px;color:#bbb}.team-animation{-webkit-animation-duration:12s;animation-duration:12s;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}.team-design{-webkit-animation-name:teamDesign;animation-name:teamDesign}.team-dev{-webkit-animation-name:teamDev;animation-name:teamDev}.team-marketing{-webkit-animation-name:teamMarketing;animation-name:teamMarketing}.team-dev-design{-webkit-animation-name:teamDevDesign;animation-name:teamDevDesign}.team-dev-design-marketing{-webkit-animation-name:teamDevDesignMarketing;animation-name:teamDevDesignMarketing}.features-section img{max-width:100%}.features-section code{padding:3px 5px;font-size:0.9em;background-color:#e7e7e7;border-radius:2px}.features-section p{max-width:750px;margin-right:auto;margin-left:auto}.features-repo-count{white-space:nowrap}.features-content-right{float:right;width:470px;text-align:left}.features-content-left{float:left;width:470px;text-align:left}.diagram-icon{position:absolute;width:53px;height:53px;line-height:55px;color:#4078c0;text-align:center;background-color:#fff;border:solid 4px #4078c0;border-radius:50px}.diagram-icon svg{margin-top:-11px}.diagram-icon-small{position:absolute;color:#4078c0}.diagram-icon-small svg{margin-top:-3px}.diagram-icon-branch{top:-13px;left:81px;-webkit-animation:bounceIn 0.6s ease-in-out 0.25s 1 normal both;animation:bounceIn 0.6s ease-in-out 0.25s 1 normal both}.diagram-icon-pr{top:89px;left:405px;-webkit-animation:bounceIn 0.6s ease-in-out 1.8s 1 normal both;animation:bounceIn 0.6s ease-in-out 1.8s 1 normal both}.diagram-icon-merge{top:-13px;left:843px;-webkit-animation:bounceIn 0.6s ease-in-out 3.7s 1 normal both;animation:bounceIn 0.6s ease-in-out 3.7s 1 normal both}.diagram-icon-commit-1{top:101px;left:240px;background-color:#fff;-webkit-animation:bounceIn 0.6s ease-in-out 1.3s 1 normal both;animation:bounceIn 0.6s ease-in-out 1.3s 1 normal both}.diagram-icon-commit-2{top:101px;left:295px;background-color:#fff;-webkit-animation:bounceIn 0.6s ease-in-out 1.4s 1 normal both;animation:bounceIn 0.6s ease-in-out 1.4s 1 normal both}.diagram-icon-commit-3{top:101px;left:350px;background-color:#fff;-webkit-animation:bounceIn 0.6s ease-in-out 1.5s 1 normal both;animation:bounceIn 0.6s ease-in-out 1.5s 1 normal both}.diagram-icon-discussion-1{top:79px;left:488px;opacity:0.3;-webkit-animation:bounceIn 0.6s ease-in-out 2s 1 normal both;animation:bounceIn 0.6s ease-in-out 2s 1 normal both}.diagram-icon-commit-4{top:101px;left:515px;background-color:#fff;-webkit-animation:bounceIn 0.6s ease-in-out 2.1s 1 normal both;animation:bounceIn 0.6s ease-in-out 2.1s 1 normal both}.diagram-icon-discussion-2{top:131px;left:542px;opacity:0.3;-webkit-animation:bounceIn 0.6s ease-in-out 2.2s 1 normal both;animation:bounceIn 0.6s ease-in-out 2.2s 1 normal both}.diagram-icon-commit-5{top:101px;left:570px;background-color:#fff;-webkit-animation:bounceIn 0.6s ease-in-out 2.3s 1 normal both;animation:bounceIn 0.6s ease-in-out 2.3s 1 normal both}.diagram-icon-discussion-3{top:79px;left:597px;opacity:0.3;-webkit-animation:bounceIn 0.6s ease-in-out 2.4s 1 normal both;animation:bounceIn 0.6s ease-in-out 2.4s 1 normal both}.diagram-icon-commit-6{top:101px;left:625px;background-color:#fff;-webkit-animation:bounceIn 0.6s ease-in-out 2.5s 1 normal both;animation:bounceIn 0.6s ease-in-out 2.5s 1 normal both}.diagram-icon-discussion-4{top:131px;left:652px;opacity:0.3;-webkit-animation:bounceIn 0.6s ease-in-out 2.6s 1 normal both;animation:bounceIn 0.6s ease-in-out 2.6s 1 normal both}.diagram-icon-commit-7{top:101px;left:680px;background-color:#fff;-webkit-animation:bounceIn 0.6s ease-in-out 2.7s 1 normal both;animation:bounceIn 0.6s ease-in-out 2.7s 1 normal both}.diagram-icon-discussion-5{top:79px;left:707px;opacity:0.3;-webkit-animation:bounceIn 0.6s ease-in-out 2.8s 1 normal both;animation:bounceIn 0.6s ease-in-out 2.8s 1 normal both}.features-branch-diagram{position:relative;margin-top:40px;margin-bottom:50px}.preload .diagram-animation{opacity:0;-webkit-animation:none !important;animation:none !important}.mobile .diagram-animation{opacity:1;-webkit-animation:none !important;animation:none !important}.features-highlight{display:inline-block;padding:10px;margin:8px 0;background-color:#e7e7e7;border-radius:3px}.features-highlight i{font-style:normal;color:#4078c0}.features-callout{display:inline-block;width:450px;padding:10px;margin-top:15px;margin-bottom:45px;font-size:14px;line-height:1.4;color:#767676;text-align:left;border:solid 1px #eee;border-radius:3px}.features-callout>p{margin-bottom:0}.features-callout .left{margin-right:10px;vertical-align:center}.features-callout .btn-fake{cursor:default}.features-callout .btn-fake:hover,.features-callout .btn-fake:active{background-color:#eee;background-image:-webkit-linear-gradient(#fcfcfc, #eee);background-image:linear-gradient(#fcfcfc, #eee);border-color:#d5d5d5;box-shadow:inherit}.features-callout .btn-fake .octicon{vertical-align:text-top}.features-copy-minor{font-size:12px;color:#555}.svn-callout{padding-top:30px;padding-left:217px;clear:both;line-height:0.8;text-align:left}.svn-callout-heading{font-size:18px;color:#444}.svn-callout-logo{float:left;margin-top:-1px;margin-left:-70px}@-webkit-keyframes teamDev{3%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}27%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}30%{color:#bbb;fill:#bbb;border-color:#ccc}}@keyframes teamDev{3%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}27%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}30%{color:#bbb;fill:#bbb;border-color:#ccc}}@-webkit-keyframes teamDesign{34%{color:#bbb;fill:#bbb;border-color:#ccc}37%{color:#6cc644;fill:#6cc644;border-color:#6cc644}60%{color:#6cc644;fill:#6cc644;border-color:#6cc644}63%{color:#bbb;fill:#bbb;border-color:#ccc}}@keyframes teamDesign{34%{color:#bbb;fill:#bbb;border-color:#ccc}37%{color:#6cc644;fill:#6cc644;border-color:#6cc644}60%{color:#6cc644;fill:#6cc644;border-color:#6cc644}63%{color:#bbb;fill:#bbb;border-color:#ccc}}@-webkit-keyframes teamMarketing{67%{color:#bbb;fill:#bbb;border-color:#ccc}70%{color:#c9510c;fill:#c9510c;border-color:#c9510c}94%{color:#c9510c;fill:#c9510c;border-color:#c9510c}}@keyframes teamMarketing{67%{color:#bbb;fill:#bbb;border-color:#ccc}70%{color:#c9510c;fill:#c9510c;border-color:#c9510c}94%{color:#c9510c;fill:#c9510c;border-color:#c9510c}}@-webkit-keyframes teamDevDesign{3%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}27%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}30%{color:#bbb;fill:#bbb;border-color:#ccc}34%{color:#bbb;fill:#bbb;border-color:#ccc}37%{color:#6cc644;fill:#6cc644;border-color:#6cc644}60%{color:#6cc644;fill:#6cc644;border-color:#6cc644}63%{color:#bbb;fill:#bbb;border-color:#ccc}}@keyframes teamDevDesign{3%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}27%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}30%{color:#bbb;fill:#bbb;border-color:#ccc}34%{color:#bbb;fill:#bbb;border-color:#ccc}37%{color:#6cc644;fill:#6cc644;border-color:#6cc644}60%{color:#6cc644;fill:#6cc644;border-color:#6cc644}63%{color:#bbb;fill:#bbb;border-color:#ccc}}@-webkit-keyframes teamDevDesignMarketing{3%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}27%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}30%{color:#bbb;fill:#bbb;border-color:#ccc}34%{color:#bbb;fill:#bbb;border-color:#ccc}37%{color:#6cc644;fill:#6cc644;border-color:#6cc644}60%{color:#6cc644;fill:#6cc644;border-color:#6cc644}63%{color:#bbb;fill:#bbb;border-color:#ccc}67%{color:#bbb;fill:#bbb;border-color:#ccc}70%{color:#c9510c;fill:#c9510c;border-color:#c9510c}94%{color:#c9510c;fill:#c9510c;border-color:#c9510c}}@keyframes teamDevDesignMarketing{3%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}27%{color:#bd2c00;fill:#bd2c00;border-color:#bd2c00}30%{color:#bbb;fill:#bbb;border-color:#ccc}34%{color:#bbb;fill:#bbb;border-color:#ccc}37%{color:#6cc644;fill:#6cc644;border-color:#6cc644}60%{color:#6cc644;fill:#6cc644;border-color:#6cc644}63%{color:#bbb;fill:#bbb;border-color:#ccc}67%{color:#bbb;fill:#bbb;border-color:#ccc}70%{color:#c9510c;fill:#c9510c;border-color:#c9510c}94%{color:#c9510c;fill:#c9510c;border-color:#c9510c}}@-webkit-keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(0.3);transform:scale(0.3)}50%{opacity:1;-webkit-transform:scale(1.05);transform:scale(1.05)}70%{-webkit-transform:scale(0.9);transform:scale(0.9)}100%{-webkit-transform:scale(1);transform:scale(1)}}@keyframes bounceIn{0%{opacity:0;-webkit-transform:scale(0.3);transform:scale(0.3)}50%{opacity:1;-webkit-transform:scale(1.05);transform:scale(1.05)}70%{-webkit-transform:scale(0.9);transform:scale(0.9)}100%{-webkit-transform:scale(1);transform:scale(1)}}@-webkit-keyframes fadeIn{0%{opacity:0}100%{opacity:1}}@keyframes fadeIn{0%{opacity:0}100%{opacity:1}}.segmented-nav-tab{display:none;margin-top:40px}.segmented-nav-tab::before{display:table;content:""}.segmented-nav-tab::after{display:table;clear:both;content:""}.segmented-nav-tab.active{display:block}.list-of-octicons{margin-bottom:28px;margin-left:26px;list-style:none}.list-of-octicons li{position:relative;margin-bottom:20px}.list-of-octicons .octicon{position:absolute;top:3px;margin-left:-22px;color:#4078c0}.about-header{height:300px;background-color:#111;background-image:url("/images/modules/about/about-header.jpg");background-position:50%;background-size:cover}.about-header.team{background-image:url("/images/modules/about/team-header.jpg")}.about-header.press{background-image:url("/images/modules/about/press-header.jpg")}.about-header.jobs{background-image:url("/images/modules/about/jobs-header.jpg")}.social-callout-twitter{padding-right:18px;margin-top:40px;margin-bottom:20px}.social-callout-twitter:hover .social-callout-twitter-logo{background-image:url("/images/icons/twitter-white.png")}.social-callout-twitter-logo{display:inline-block;width:32px;height:32px;vertical-align:middle;background:url("/images/icons/twitter.png") 0 0 no-repeat;background-size:32px auto}.account-membership-form .become-a-member,.account-membership-form .already-a-member{display:none}.account-membership-form.is-member .already-a-member{display:block}.account-membership-form.is-not-member .become-a-member{display:block}.billing-plans tbody td{width:25%;vertical-align:middle}.billing-plans .current{background-color:#f2ffed}.billing-plans .name{font-size:14px;font-weight:bold;color:#333}.billing-plans .coupon{font-size:12px}.billing-plans .coupon td{color:#fff;background-color:#6cc644}.billing-plans .coupon .text-right{white-space:nowrap}.billing-plans .coupon.expiring td{background-color:#df6e00}.billing-plans .coupon.expiring .coupon-label::after{border-bottom-color:#df6e00}.billing-plans tbody>.selected{background-color:#fdffce}.coupon-label{position:relative;padding:9px;margin:-9px}.coupon-label::after{position:absolute;bottom:100%;left:15px;width:0;height:0;pointer-events:none;content:" ";border:solid transparent;border-width:5px;border-bottom-color:#6cc644}.boxed-group-table .toggle-currency{font-size:11px;font-weight:normal}.is-hidden,.has-removed-contents{display:none}.currency-notice{margin-bottom:10px}.org-login{margin-top:-30px;margin-bottom:30px}.org-login img{width:450px;padding:1px;margin:10px -25px;border:1px solid #ccc}.plan-notice{padding:10px;margin-bottom:0;border-top:1px solid #eee}.avatar-cell{width:40px}.member-list-item .member-username{display:inline}.member-list-item .member-link{display:inline}.actor-and-action{font-weight:bold}.vertical-separator{margin-right:8px;margin-left:5px;border-left:1px solid #808080}.audit-log-search .audit-search-form{margin-bottom:10px}.audit-log-search .audit-results-actions{margin:15px 0}.audit-log-search .audit-search-clear{margin-bottom:0}.auth-form{width:340px;margin:0 auto}.auth-form .password-note{margin:15px 0;text-align:center}.auth-form-header{position:relative;margin:0;color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.3);background-color:#829aa8;border:1px solid #768995;border-radius:3px 3px 0 0}.auth-form-header h1{font-size:16px}.auth-form-header h1 a{color:#fff}.auth-form-header .octicon{position:absolute;top:10px;right:20px;color:rgba(0,0,0,0.4);text-shadow:0 1px 0 rgba(255,255,255,0.1)}.auth-form-message{max-height:140px;padding:20px 20px 10px;overflow-y:scroll;border:1px solid #d8dee2;border-radius:3px}.auth-form-message ul{padding-left:inherit;margin-bottom:inherit}.auth-form-body{padding:20px;font-size:14px;background-color:#fff;border:1px solid #d8dee2;border-top:0;border-radius:0 0 3px 3px}.auth-form-body .input-block{margin-top:5px;margin-bottom:15px}.auth-form-body p{margin:10px 0}.auth-form-body ul{padding-left:inherit;margin-bottom:inherit}.two-factor-help{position:relative;padding:10px 10px 10px 36px;margin:60px 0 auto auto;border:1px solid #eaeaea;border-radius:3px}.two-factor-help h4{margin-top:0;margin-bottom:5px}.two-factor-help .octicon-device-mobile{position:absolute;top:10px;left:10px}.two-factor-help .octicon-key{position:absolute;left:10px}.two-factor-help .btn-sm{float:right}.two-factor-help ul{list-style-type:none}.u2f-send-code-spinner{position:relative;bottom:2px;display:none;vertical-align:bottom}.loading .u2f-send-code-spinner{display:inline}.u2f-login-spinner{position:relative;top:2px}.u2f-auth-header{padding-bottom:10px;margin-bottom:20px;border-bottom:1px solid #eaeaea}.u2f-auth-form-body{padding:30px 30px 20px;text-align:center}.u2f-auth-form-body button{margin-top:20px}.u2f-auth-form-body .u2f-enabled{display:block}.u2f-auth-form-body .u2f-disabled{display:none}.u2f-auth-form-body.unavailable .u2f-enabled{display:none}.u2f-auth-form-body.unavailable .u2f-disabled{display:block}.u2f-auth-icon{color:#aaa}.flash.sms-error,.flash.sms-success{display:none;margin:0 0 10px}.is-sent .sms-success{display:block}.is-sent .sms-error{display:none}.is-not-sent .sms-success{display:none}.is-not-sent .sms-error{display:block}.session-authentication{background-color:#f9f9f9}.session-authentication .header-logged-out{padding:40px 0 20px;background-color:transparent;border-bottom:0}.session-authentication .header-logo{display:block;width:48px;height:48px;margin:0 auto;color:#333}.session-authentication .flash{padding:15px 20px;margin:0 auto;margin-bottom:10px;font-size:13px;border-style:solid;border-width:1px;border-radius:5px}.session-authentication .flash .container{width:auto}.session-authentication .flash .flash-close{height:40px}.session-authentication .flash.is-signed-in,.session-authentication .flash.is-signed-out{width:100%;border-top:0;border-right:0;border-left:0;border-radius:0}.session-authentication .auth-form .password-note{padding:0 20px}.session-authentication .auth-form label{display:block;margin-bottom:7px;font-weight:normal}.session-authentication .auth-form .btn{margin-top:20px}.session-authentication .auth-form .u2f-error{margin-bottom:0}.session-authentication .label-link{float:right;font-size:12px}.session-authentication .auth-form-header{padding:10px 0;margin-bottom:15px;color:#333;text-align:center;text-shadow:none;background-color:transparent;border:0}.session-authentication .auth-form-header h1{font-size:24px;font-weight:300;letter-spacing:-0.5px}.session-authentication .auth-form-body{border-top:1px solid #d8dee2;border-radius:5px}.session-authentication .auth-form-body.u2f-auth-form-body{padding:20px}.session-authentication .create-account-callout{padding:15px 20px;text-align:center;border:1px solid #d8dee2;border-radius:5px}.session-authentication .two-factor-help{padding:0 0 0 20px;margin-top:20px;border:0}.session-authentication .two-factor-help .octicon-device-mobile{top:3px;left:0}.session-authentication .two-factor-help .octicon-key{top:0;left:0}.session-authentication .site-footer{border-top:0}.session-authentication .site-footer-links{text-align:center}.session-authentication .site-footer-links a{color:#9b9b9b}.session-authentication.enterprise .header-logo{width:204px;height:29px}.session-authentication.enterprise .header-logged-out{padding:48px 0 28px;background-color:transparent}.session-authentication.hosted .header-logo{width:48px;height:48px}.session-authentication.hosted .header-logged-out{padding:40px 0 20px;background-color:transparent}.cvv-hint{position:relative;padding-right:15px}.cvv-hint:hover .cvv-hint-tooltip{display:block}.cvv-hint-tooltip{position:absolute;left:-5px;z-index:1000;display:none;padding:15px;background-color:#fff;border:1px solid #d0d0d0;border-radius:3px;-webkit-transform:translateX(-100%) translateY(-50%);transform:translateX(-100%) translateY(-50%)}.credit-card{position:relative;width:250px;height:150px;padding:20px;margin-top:5px;background-color:#f5f5f5;border-radius:10px}.credit-card.amex{margin-top:15px}.credit-card.amex .title{position:relative;top:-5px;z-index:1;font-family:"Arial Black", "Arial Bold", Gadget, sans-serif;color:#999;text-align:center;letter-spacing:1px;-webkit-transform:scale(1.3, 1);transform:scale(1.3, 1)}.credit-card.amex .card-number{position:relative;display:inline-block;margin-top:40px;font-size:15px;white-space:nowrap}.credit-card.amex .gladiator{position:absolute;top:50px;left:50%;width:70px;height:80px;margin-left:-35px;background-color:#f5f5f5;border:3px solid #fff;border-radius:50%;box-shadow:0 0 1px #aaa}.credit-card.normal .strap{height:20px;margin:-5px -20px 15px;background-color:#555}.credit-card.normal .signature{display:inline-block;width:150px;height:30px;font-family:"Brush Script MT", cursive;font-size:17px;line-height:33px;color:#767676;text-indent:10px;letter-spacing:-1px;white-space:nowrap;background-color:#fff}.credit-card .cvv{position:relative;top:-10px;left:-7px;display:inline-block;padding:2px 5px;font-family:monospace;font-size:10px;line-height:1;text-align:center;border:2px solid #f00;border-top-left-radius:20px 10px;border-top-right-radius:20px 10px;border-bottom-right-radius:20px 10px;border-bottom-left-radius:20px 10px}.credit-card .cvv span{position:absolute;right:100%;margin-right:5px;color:#767676}.credit-card .text{display:block;font-family:monospace;font-size:7px;font-weight:bold;line-height:1.1;text-transform:uppercase}.billing-addon-items table input{width:5em}.billing-addon-items td{vertical-align:middle;border-bottom:0}.billing-addon-items td.fixed{width:150px}.billing-addon-items td.black{color:#000}.billing-addon-items tr{border-bottom:1px solid #eee}.billing-addon-items tr:last-child{border-bottom-width:0}.billing-addon-items tr:nth-child(even){background-color:#fafafa}.billing-addon-items tr.total-row{color:#bd2c00;background-color:#fff}.billing-addon-items tr.dark-row{background-color:#fafafa;border-bottom-width:1px}.billing-addon-items .new-addon-items{margin-left:5px}.billing-addon-items .addon-cost{color:#999}.billing-addon-items .discounted-original-price{color:#666}.billing-addon-items .form-submit,.billing-addon-items .payment-method{margin-left:10px}.billing-addon-items .payment-summary{margin-right:10px;margin-left:10px}.billing-credit-card .javascript-disabled-overlay{position:absolute;top:0;left:0;z-index:1;display:none;width:100%;height:100%;background-color:#fff;opacity:0.5}.billing-credit-card.disabled .javascript-disabled-overlay,.billing-credit-card.unsupported .javascript-disabled-overlay{display:block}.billing-actions{padding-bottom:10px}.help.billing-next-payment-help{margin-top:0}.billing-extra-box{padding-left:10px;margin:10px 0 0;border-left:3px solid #eee}.billing-section{padding:15px 10px;line-height:1.5em;border-bottom:1px solid #eee}.billing-section.oneliner{padding-bottom:14px}.billing-section.oneliner .action-button{margin-top:-4px;margin-bottom:-4px}.billing-section p{margin:10px 0 0}.billing-section .disabled-message{color:#bd2c00}.billing-section .action-button{float:right;margin-bottom:5px;margin-left:10px}.billing-section .btn-octicon{float:right;padding:4px;margin-left:5px}.billing-section .section-label{position:absolute;width:85px;font-weight:normal;color:#767676;text-align:right}.billing-section .section-content{margin-left:100px;color:#333}.billing-section .pending-invitations-link,.billing-section .subtle-link{color:#767676}.billing-section:last-child{border-bottom:0}.billing-section.info-section{overflow:hidden;color:#767676;background:#f9f9f9;border-bottom:0}.billing-section.info-section .octicon-info{font-size:30px;color:#ddd}.billing-section .usage-bar{max-width:304px}.usage-bar{width:100%;margin:5px 0 0;background:#eee;border-radius:20px}.usage-bar.exceeded .progress{background:#bd2c00}.usage-bar .progress{position:relative;max-width:100%;height:5px;background:#67d07c;border-radius:20px;-webkit-transition:width 0.3s;transition:width 0.3s}.usage-bar .progress.no-highlight{background:#999}.live-update-seats-usage{margin:10px 0 5px}.billing-per-seat-callout{position:relative}.billing-per-seat-callout::after{position:absolute;top:1px;left:25px;display:block;width:12px;height:12px;content:"";background-color:#fafafa;box-shadow:1px -1px 0 0 #eee;-webkit-transform:translate(-50%, -50%) rotate(-45deg);transform:translate(-50%, -50%) rotate(-45deg)}.per-seat-help{padding:10px;border-bottom:1px solid #eee}.per-seat-help .per-seat-help-heading{margin:5px 0 10px;color:#bd2c00}.per-seat-help .per-seat-help-heading .octicon-alert{margin-right:3px;font-size:14px}.per-seat-help-list{list-style:none}.per-seat-help-list li{margin:5px 0}.per-seat-help-list .octicon-chevron-right{float:left;margin:0 6px 7px 7px;color:#999}.billing-data-usage-meter{margin-bottom:15px}.billing-data-usage-meter:last-child{margin-bottom:5px}.packs-table .desc{width:1%;white-space:nowrap}.pack-upgrade-plus{font-size:16px;font-weight:bold;color:#6cc644;text-align:center}.lfs-data-icon{color:#767676;text-align:center}.lfs-data-icon.dark{color:#333}.lfs-data-icon.octicon-database{margin-right:3px;margin-left:2px}.setup-wrapper .paypal-container{margin-bottom:30px}.setup-wrapper .paypal-logged-in .paypal-container{margin-bottom:10px}.payment-methods{position:relative}.payment-methods .selected-payment-method{display:none}.payment-methods .selected-payment-method::before{display:table;content:""}.payment-methods .selected-payment-method::after{display:table;clear:both;content:""}.payment-methods .selected-payment-method.active{display:block}.payment-methods .pay-with-header{margin:5px 0}.payment-methods .pay-with-paypal .setup-creditcard-form,.payment-methods .pay-with-paypal .paypal-form-actions,.payment-methods .pay-with-paypal .terms,.payment-methods .pay-with-paypal .paypal-signed-in,.payment-methods .pay-with-paypal .paypal-down-flash,.payment-methods .pay-with-paypal .loading-paypal-spinner{display:none}.payment-methods.paypal-loading .loading-paypal-spinner{display:block}.payment-methods.paypal-down .paypal-down-flash{display:block}.payment-methods.paypal-logged-in .paypal-sign-in{display:none}.payment-methods.paypal-logged-in .setup-creditcard-form,.payment-methods.paypal-logged-in .paypal-form-actions,.payment-methods.paypal-logged-in .terms,.payment-methods.paypal-logged-in .paypal-signed-in{display:block}.payment-methods.has-paypal-account .paypal-sign-in{display:none}.payment-methods.has-paypal-account .paypal-signed-in{display:block}.contact-us-alert{padding:10px;padding-left:30px;font-size:12px;background-color:#fff9ea;border-bottom:1px solid #eee}.contact-us-alert .octicon{position:absolute;margin-left:-20px;color:#767676}.setup-main .contact-us-alert{margin:0 0 20px;border:1px solid #ddd;border-radius:3px}.paypal-label{margin:15px 0 10px;font-weight:bold}.paypal-container{display:inline-block;margin-bottom:15px;vertical-align:top;background-color:#f9f9f9;border-radius:4px}.braintree-paypal-loggedin{padding:11px 16px !important;background-position:12px 50% !important;border:1px solid #ddd !important;border-radius:4px}.bt-pp-name{margin-left:20px !important}.bt-pp-email{margin-left:15px !important}.bt-pp-cancel{font-size:0 !important;line-height:1 !important;color:#a00 !important;text-decoration:none !important}.payment-history .no-payments{margin:0;border-top:0}.payment-history .id,.payment-history .date,.payment-history .method,.payment-history .receipt,.payment-history .status,.payment-history .description,.payment-history .amount{width:1%;white-space:nowrap}.payment-history .receipt{text-align:center}.payment-history .currency,.payment-history .status{color:#767676}.payment-history .status-icon{width:14px;text-align:center}.payment-history .succeeded .status{color:#6cc644}.payment-history .refunded,.payment-history .failed{background:#f9f9f9}.payment-history .refunded td,.payment-history .failed td{opacity:0.5}.payment-history .refunded .receipt,.payment-history .refunded .status,.payment-history .failed .receipt,.payment-history .failed .status{opacity:1}.payment-history .refunded .status{color:#999}.payment-history .failed .status{color:#bd2c00}.paypal-icon{margin:0 2px 0 1px;vertical-align:middle}.inline-form-action{display:inline}.boxed-group .boxed-group-content{margin:10px}.currency-container .local-currency,.currency-container .local-currency-block{display:none}.currency-container.open .local-currency{display:inline}.currency-container.open .local-currency-block{display:block}.currency-container.open .default-currency{display:none}.strong-label{display:inline-block;margin-bottom:5px;font-weight:bold}.discounted-original-price{font-weight:normal;color:#767676;text-decoration:line-through}.billing-managers-abilities-list{list-style:none}.billing-managers-abilities-list li{margin-bottom:6px}.billing-managers-abilities-list .octicon{width:24px;text-align:center}.billing-managers-abilities-list .octicon-check{color:#6cc644}.billing-managers-abilities-list .octicon-x{color:#bd2c00}.billing-manager-input{width:500px}.billing-manager-banner{padding:30px 20px;margin-bottom:30px;overflow:hidden;background:#f9f9f9;border-bottom:1px solid #eee}.billing-manager-banner .container{position:relative}.billing-manager-banner-text{margin-left:210px;font-size:14px;color:#555}.billing-manager-banner-text .btn{margin-top:8px;margin-right:8px}.billing-manager-banner-title{font-size:12px;font-weight:bold;color:#767676}.billing-manager-icon{position:absolute;top:-35px;left:0;width:180px;height:180px;font-size:180px;color:#e0e0e0}.golden-ticket-banner{margin-top:30px;margin-bottom:15px;text-align:center;border-top:1px solid #e6d445}.golden-ticket{height:60px;margin-top:-30px}.golden-ticket-button{float:left;width:50%;padding:30px 20px;font-size:18px;font-weight:normal}.golden-ticket-button .octicon{margin-right:10px;vertical-align:middle}.golden-ticket-button:first-child{border-radius:3px 0 0 3px}.golden-ticket-button:last-child{border-left:0;border-radius:0 3px 3px 0}.golden-ticket-confirm .setup-header{text-align:center;border:0}.seats-change-arrow{margin:0 10px}.billing-note-block{margin:15px 0}.plan-choice{position:relative;display:block;padding:15px;padding-left:40px;font-weight:normal;background-color:#fafafa;border:1px solid #e0e0e0}.plan-choice.open{background-color:#fff}.plan-choice-free{border-radius:3px 3px 0 0}.plan-choice-paid{margin-bottom:20px;border-top:0;border-radius:0 0 3px 3px}.plan-choice-radio{position:absolute;top:18px;left:15px}.plan-choice-exp{margin-top:5px;font-size:12px;color:#767676}.plan-choice-price{margin-top:5px;font-size:12px}.seat-field{width:50px;margin-right:5px}.billing-form-title{font-size:16px}.billing-line-items{margin-top:10px}.billing-line-item{padding:10px 0;font-size:12px;list-style:none;border-top:1px solid #e0e0e0}.billing-line-item::before{display:table;content:""}.billing-line-item::after{display:table;clear:both;content:""}.billing-line-item-last{font-weight:bold;border-top-width:3px}.line-item-value{float:right}.billing-confirmation{width:750px;margin-top:50px}.billing-confirmation .light-label{margin:5px 0;font-weight:normal;color:#999}.billing-confirmation .pending-invitations{font-weight:normal}.billing-confirmation .form-value{margin:3px 0 15px;font-weight:bold}.billing-confirmation .paypal-container{margin-bottom:0}.billing-confirmation-header{padding:5px 0;margin:20px 0;border-bottom:1px solid #e0e0e0}.price-side-note{color:#767676}.price-box{padding:15px}.price-box h2{margin:5px 0}.billing-confirmation-box{margin:20px 0;border:2px solid #6cc644;border-radius:3px}.billing-confirmation-box .boxes::before{display:table;content:""}.billing-confirmation-box .boxes::after{display:table;clear:both;content:""}.billing-confirmation-box.twoup .price-box{float:left;width:50%}.billing-confirmation-box.twoup .price-box:first-child{border-right:1px solid #e0e0e0}.billing-confirmation-box.twoup .price-box:last-child{margin-left:-1px;border-left:1px solid #e0e0e0}.next-charge-box{padding:10px 15px;background:#f9f9f9;border-top:1px solid #e0e0e0}.inline-payment-box{margin:20px 0}.condensed-payment-methods .radio-input:checked+.radio-label .cancel-changing-payment{display:inline}.condensed-payment-methods .cancel-changing-payment{display:none}.condensed-payment-methods .vat-field,.condensed-payment-methods .cc-field,.condensed-payment-methods .expiration-field,.condensed-payment-methods .country-field,.condensed-payment-methods .vat-field{margin-top:15px}.condensed-payment-methods .vat-field{width:100%;margin-bottom:0}.condensed-payment-methods .cc-field{width:42%}.condensed-payment-methods .expiration-field,.condensed-payment-methods .state-field{width:30%}.condensed-payment-methods .expiration-field{white-space:nowrap}.condensed-payment-methods .expiration-field .select{width:40%}.condensed-payment-methods .expiration-divider{width:20%;line-height:30px;text-align:center}.condensed-payment-methods .cvv-field,.condensed-payment-methods .postcode-field{width:28%;margin-top:15px;margin-bottom:0}.condensed-payment-methods .state-field.column,.condensed-payment-methods .postcode-field.column{margin-top:15px;margin-bottom:0}.condensed-payment-methods .country-field{width:42%}.condensed-payment-methods .state-field{width:30%}.condensed-payment-methods .is-international .country-field{width:72%}.condensed-payment-methods .is-international.no-postcodes .country-field{width:100%}.heat[data-heat="1"]{background-color:#ffeca7}.heat[data-heat="2"]{background-color:#ffdd8c}.heat[data-heat="3"]{background-color:#ffdd7c}.heat[data-heat="4"]{background-color:#fba447}.heat[data-heat="5"]{background-color:#f68736}.heat[data-heat="6"]{background-color:#f37636}.heat[data-heat="7"]{background-color:#ca6632}.heat[data-heat="8"]{background-color:#c0513f}.heat[data-heat="9"]{background-color:#a2503a}.heat[data-heat="10"]{background-color:#793738}.blame-breadcrumb .css-truncate-target{max-width:680px}.blame-commit,.blame-commit+.blame-line{border-top:1px solid #e9e9e9}.blame-container{margin-top:-1px}.blame-blob-num{background-color:#fdfdfd}.blame-commit-info{position:relative;width:350px;min-width:350px;max-width:350px;padding:8px 10px;vertical-align:top}.blame-commit-avatar{float:left;margin-right:5px}.blame-commit-title{display:inline-block;max-width:230px;overflow:hidden;line-height:1.1;text-overflow:ellipsis;white-space:nowrap;vertical-align:top}.blame-commit-title,.blame-commit-title .message{font-weight:bold;color:#333}.blame-sha{float:right;font:11px Consolas, "Liberation Mono", Menlo, Courier, monospace}.blame-commit-meta{overflow:hidden;font-size:12px;line-height:1.1;color:#767676;text-overflow:ellipsis;white-space:nowrap}.line-age{width:2px;padding:0 1px}.line-age-legend{float:right;margin-top:-25px;font-size:12px;color:#767676}.line-age-legend ol{display:inline-block;margin:0 5px;list-style:none}.line-age-legend ol li{display:inline-block;width:8px;height:10px}.editor-abort{display:inline;font-size:14px}.blob-interaction-bar{position:relative;background-color:#f2f2f2;border-bottom:1px solid #e5e5e5}.blob-interaction-bar::before{display:table;content:""}.blob-interaction-bar::after{display:table;clear:both;content:""}.blob-interaction-bar .octicon-search{position:absolute;top:10px;left:10px;font-size:12px;color:#767676}.blob-filter{width:100%;padding:4px 20px 5px 30px;font-size:12px;border:0;border-radius:0;outline:none}.blob-filter:focus{outline:none}.html-blob{margin-bottom:15px}.blog-title{color:#333}.blog-title-group{font-size:20px;font-weight:normal;line-height:34px}.blog-search{position:relative;float:right}.blog-search .blog-search-input{width:200px;padding-left:28px}.blog-search .octicon-search{position:absolute;top:9px;left:7px;z-index:5;color:#767676}.blog-search-results em{padding:0.1em;background-color:#faffa6}.blog-aside{float:right;width:200px}.blog-aside .btn{margin-bottom:20px;text-align:center}.blog-aside .rss{display:inline-block;margin-left:5px;color:#999}.blog-aside .rss .octicon{float:left;margin-right:5px;color:#c9510c}.blog-content{width:685px}.blog-content h1,.blog-content h2,.blog-content h3{font-weight:500}.blog-content .markdown-body::after{clear:none}.blog-content .markdown-body h2{font-size:20px}.blog-content .markdown-body h3{font-size:18px}.blog-draft-indicator{color:#bd2c00}.blog-post{margin-bottom:60px}.blog-post-meta{margin-bottom:20px;color:#999;list-style:none}.blog-post-meta .meta-item{display:inline;padding-right:20px}.blog-post-meta a{color:#999}.blog-post-meta .author-avatar{vertical-align:top;border-radius:3px}.blog-post-title{margin-top:0;margin-bottom:10px;font-size:32px}.blog-home{display:block;float:left;width:16px;margin-top:-2px;margin-left:-25px;color:#ccc}.blog-home:hover{color:#767676}.blog-home .octicon{vertical-align:middle}.blog-post-body{font-size:16px;line-height:1.6;color:#444}.blog-post-body .anchor{display:none}.blog-post-body img{padding:3px;border:1px solid #d8d8d8}.blog-post-body img.emoji{padding:0;border:0}.blog-post-body iframe{width:100%;border:0}.blog-feedback{margin:50px 0;background-color:#fafafa;border:1px solid #ddd;border-bottom-color:#ccc;border-radius:3px;box-shadow:inset 0 1px 0 #fff, 0 1px 5px #f1f1f1}.blog-feedback-header{padding:10px;margin:0;font-size:14px;font-weight:bold;border-bottom:1px solid #ddd;box-shadow:0 1px 0 #fff}.blog-feedback-header.with-twitter{background:url("/images/icons/twitter.png") 648px 1px no-repeat;background-size:32px auto}.blog-feedback-description{padding:10px;margin:0;color:#767676}.Box{background-color:#fff;border:1px solid #ddd;border-radius:3px}.Box-header{padding:12px;overflow:hidden;background-color:#f5f5f5;border-bottom:1px solid #ddd;border-top-left-radius:2px;border-top-right-radius:2px}.Box-title{overflow:hidden;font-weight:bold}.Box-close{padding:12px 12px;margin:-12px -12px;color:#888}.Box-close:hover{color:#4078c0}.Box-body{padding:12px 12px}.Box-body-row{padding:12px 12px;margin:12px -12px -12px;list-style-type:none;border-top:1px solid #eee}.Box-body-row:first-of-type{margin-top:-12px;border-top:0}.Box-body-row.unread{box-shadow:2px 0 0 #4078c0 inset}.Box-body-row.navigation-focus .Box-row--drag-button{color:#4078c0;opacity:100}.Box-body-row.navigation-focus.sortable-chosen{background-color:#fafafa}.Box-body-row.navigation-focus.sortable-ghost{background-color:#f5f5f5}.Box-body-row.navigation-focus.sortable-ghost .Box-row--drag-hide{opacity:0}@media (min-width: 1004px){.Box-body-row--highlight.navigation-focus,.Box-body-row--highlight:hover{background-color:#f5f5f5}}.Box-body-row--highlight .Box-row-link{color:#333;text-decoration:none;word-break:break-word}.Box-body-row--highlight .Box-row-link:hover{color:#4078c0;text-decoration:none}.Box-row--drag-button{opacity:0}.Box-row--checkbox{position:relative;top:1px}.Box-footer{padding:12px;border-top:1px solid #eee}.Box--scrollable{max-height:324px;overflow:scroll}.merge-pr{padding-top:10px;margin:20px 0 0;border-top:1px solid #ddd}.merge-pr.open .merge-branch-form{display:block}.merge-pr.open .branch-action{display:none}.status-heading{margin-bottom:1px}.build-statuses-list{max-height:0;padding:0;margin:15px -15px -16px -55px;overflow-y:auto;border:solid #eee;border-width:1px 0 0;-webkit-transition:max-height 0.25s ease-in-out;transition:max-height 0.25s ease-in-out}.statuses-toggle-opened{display:none}.build-status-item{position:relative;padding:10px 15px 10px 53px;background-color:#fafafa;border-bottom:1px solid #eee}.build-status-item:last-child{border-bottom:0}.build-status-item .css-truncate-target{width:74%;max-width:74%}.build-status-item .creator{position:absolute;top:9px;left:20px}.dropdown-menu .build-status-item .creator{position:relative;top:-2px;left:auto;float:left;margin-right:4px;vertical-align:middle}.status-meta{color:#767676}.branch-action-item-icon{float:left;margin-left:-40px}.build-status-icon{width:16px;text-align:center}.build-status-details{margin-left:10px}.merge-pr-more-commits{margin-top:10px;margin-bottom:10px;margin-left:64px;font-size:12px;color:#767676}.branch-action{padding-left:64px;margin-top:15px;margin-bottom:15px}.branch-action .merge-branch-heading{margin-bottom:4px}.branch-action-icon{float:left;width:48px;height:48px;padding:8px;margin-left:-64px;color:#fff;text-align:center;border-radius:3px}.branch-action-body{position:relative;background-color:#fff;border:1px solid #ddd;border-radius:3px}.branch-action-body::after,.branch-action-body::before{position:absolute;top:11px;right:100%;left:-16px;display:block;width:0;height:0;pointer-events:none;content:" ";border-color:transparent;border-style:solid solid outset}.branch-action-body::after{margin-top:1px;margin-left:2px;border-width:7px;border-right-color:#fff}.branch-action-body::before{border-width:8px;border-right-color:#ddd}.branch-action-body .spinner{display:block;float:left;width:32px;height:32px;margin-right:15px;background:url("/images/spinners/octocat-spinner-32.gif") no-repeat}.branch-action-body .merge-message,.branch-action-body .merge-branch-form{padding:15px;background-color:#fafafa;border-top:solid 1px #e5e5e5;border-bottom-right-radius:2px;border-bottom-left-radius:2px}.post-merge-message{padding:15px}.branch-action-item{padding:15px 15px 15px 55px;line-height:1.4}.branch-action-item+.branch-action-item,.branch-action-item+.mergeability-details{border-top:1px solid #e5e5e5}.branch-action-item.open>.build-statuses-list{max-height:215px;margin-bottom:-15px}.branch-action-item.open .statuses-toggle-opened{display:inline}.branch-action-item.open .statuses-toggle-closed{display:none}.branch-action-btn{margin-left:15px}.branch-action-item-simple{padding-left:15px}.branch-action-item-simple .build-statuses-list{margin-left:-15px}.branch-action-item-simple .build-status-item{padding-left:12px}.branch-action-state-clean .branch-action-icon{background-color:#6cc644}.branch-action-state-clean .branch-action-body{border-color:#95c97e}.branch-action-state-clean .branch-action-body::before{border-right-color:#95c97e}.branch-action-state-unknown .branch-action-icon,.branch-action-state-unstable .branch-action-icon{background-color:#cea61b}.branch-action-state-unknown .branch-action-body,.branch-action-state-unstable .branch-action-body{border-color:#e2cc7a}.branch-action-state-unknown .branch-action-body::before,.branch-action-state-unstable .branch-action-body::before{border-right-color:#e2cc7a}.branch-action-state-merged .branch-action-icon{background-color:#6e5494}.branch-action-state-merged .branch-action-body{border-color:#cbc0db}.branch-action-state-merged .branch-action-body::before{border-right-color:#cbc0db}.branch-action-state-dirty .branch-action-icon,.branch-action-state-closed-dirty .branch-action-icon{background-color:#888}.branch-action-state-error .branch-action-icon{background-color:#d84837}.branch-action-state-error .branch-action-body{border-color:#e97a74}.branch-action-state-error .branch-action-body::before{border-right-color:#e97a74}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){.branch-action-body .spinner{background-image:url("/images/spinners/octocat-spinner-64.gif");background-size:32px 32px}}.merge-branch-form{display:none;margin:15px 0}.merge-branch-form .commit-form{border-color:#95c97e}.merge-branch-form .commit-form::before{border-right-color:#95c97e}.merge-branch-form.error .commit-form,.merge-branch-form.danger .commit-form{border-color:#e97a74}.merge-branch-form.error .commit-form::before,.merge-branch-form.danger .commit-form::before{border-right-color:#e97a74}.merge-button-matrix-merge-form .merge-branch-form{display:block}.completeness-indicator{width:30px;height:30px;text-align:center}.completeness-indicator .octicon{display:block;margin-top:7px;margin-right:auto;margin-left:auto}.completeness-indicator-success{color:#fff;background-color:#6cc644;border-radius:50%}.completeness-indicator-error{color:#fff;background-color:#bd2c00;border-radius:50%}.completeness-indicator-problem{color:#fff;background-color:#888;border-radius:50%}.pull-merging .pull-merging-error{display:none}.pull-merging.is-error .pull-merging-error{display:block}.pull-merging.is-error .merge-pr{display:none}p.recently-touched-branches-description{margin:0}.recently-touched-branches{padding:0;margin:5px 0 10px;color:#4c4a42;background-color:#fff9ea;border:solid 1px #dfd8c2;border-radius:3px}.recently-touched-branches.default-branch{padding:7px 7px 7px 0;background-color:#e2eef9;border-color:#bac6d3}.recently-touched-branches li{height:36px;padding:5px;margin:0;line-height:23px;list-style-type:none;border-bottom:1px solid #e5e2c8}.recently-touched-branches li:last-child{border-bottom:0}.recently-pushed-branch-actions{float:right}.recently-pushed-branch-details{display:inline-block;margin:0 0 0 7px;font-size:13px;line-height:26px;color:#a19e7f}.recently-pushed-branch-details a{color:#6b694f}.recently-pushed-branch-details .css-truncate-target{max-width:400px}.recently-pushed-branch-details.default-branch{color:#325472}.recently-pushed-branch-details .default-branch-link{color:#4078c0}.range-editor{position:relative;padding:5px 15px 5px 40px;margin-top:15px;margin-bottom:15px;background-color:#fafafa;border:1px solid #e5e5e5;border-radius:3px}.range-editor .dots{font-size:16px}.range-editor .select-menu{position:relative;display:inline-block}.range-editor .select-menu.fork-suggester{display:none}.range-editor .branch-name{line-height:22px}.range-editor .branch .css-truncate-target,.range-editor .fork-suggester .css-truncate-target{max-width:180px}.range-editor .pre-mergability{display:inline-block;padding:5px;line-height:26px;vertical-align:middle}.range-editor .pre-mergability .octicon{vertical-align:text-bottom}.range-editor.is-cross-repo .select-menu.fork-suggester{display:inline-block}.range-editor-icon{float:left;margin-top:10px;margin-left:-25px;color:#767676}.gh-header-new-pr{margin-bottom:15px}.gh-header-new-pr .gh-header-meta{padding-bottom:0;margin-top:5px;border-bottom:0}.gh-header-new-pr .branch-name{display:inline}.compare-pr-header{display:none}.is-pr-composer-expanded .compare-show-header{display:none}.is-pr-composer-expanded .compare-pr-header{display:block}.range-cross-repo-pair{display:inline-block;padding:5px;white-space:nowrap}ul.comparison-list{width:350px;margin:25px auto 15px;font-size:14px;text-align:left;background:#fff;border:1px solid #ddd;border-radius:3px}ul.comparison-list>li{padding:7px 10px;list-style-type:none;border-top:1px solid #eee}ul.comparison-list>li a{font-weight:bold}ul.comparison-list>li em{float:right;font-style:normal;color:#767676}ul.comparison-list>li .octicon{position:relative;top:1px;color:#aaa}ul.comparison-list>li .css-truncate-target{max-width:200px}ul.comparison-list>li.title{font-size:12px;font-weight:bold;color:#aaa;text-transform:uppercase;background:#fafafa;border-top:0;border-top-left-radius:3px;border-top-right-radius:3px}.recently-touched-branches-wrapper{margin:10px 0;clear:both}.branches .page-header{margin-bottom:20px}.branches .clear-search{display:none}.branches .loading-overlay{position:absolute;top:0;z-index:20;display:none;width:100%;height:100%;padding-top:50px;text-align:center;background-color:rgba(255,255,255,0.7)}.branches .loading-overlay .spinner{display:inline-block}.branches.is-search-mode .clear-search{display:inline-block}.branches.is-loading .loading-overlay{display:block}.branches .status{display:inline-block;width:16px;text-align:center}.branches .status .octicon{position:relative}.branches .status .octicon-primitive-dot{width:10px}.branches .pull-request-link{top:0;display:inline;padding:2px 5px;line-height:1em}.branches .branch-actions{position:relative;top:-3px;right:-4px;float:right}.branches .branch-actions form{display:inline}.branches .branch-actions .octicon{width:16px;text-align:center}.branch-groups{position:relative}.branch-group{width:100%;margin-bottom:20px;border-radius:3px}.branch-group::before{display:table;content:""}.branch-group::after{display:table;clear:both;content:""}.branch-group-heading{padding:6px 12px;text-shadow:0 1px 0 #fff;background:#f5f5f5;border:1px solid #ddd;border-bottom:0}.branch-group-heading+.branch-summary{border-top:1px solid #ddd}.branch-group-heading .branch-name{color:#fff;text-shadow:none;background:#767676}.branch-group-name{font-weight:bold;color:#767676}.branch-group-heading:first-child,.branch-summary:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.branch-group-heading:last-child,.branch-summary:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.branches-view-switcher{display:inline-block;vertical-align:middle}.branch-search{position:relative;float:right;vertical-align:middle}.branch-search .clear-search{position:absolute;top:9px;right:12px;color:#999}.branch-search-field{width:250px;padding-right:25px}.no-results-message{padding:12px;color:rgba(0,0,0,0.5);text-align:center;border:1px solid #ddd;border-radius:0 0 3px 3px}.branch-summary{padding:12px;color:rgba(0,0,0,0.5);border:1px solid #ddd;border-bottom:0}.branch-summary:last-child{border-bottom:1px solid #ddd}.branch-summary .branch-spinner{display:none;vertical-align:text-bottom}.branch-summary.loading .branch-delete-icon{display:none}.branch-summary.loading .branch-spinner{display:inline-block}.branch-summary.is-deleted .existing-branch-summary{display:none}.branch-summary.is-deleted .deleted-branch-summary{display:block}.deleted-branch-summary{display:none}.deleted-branch-summary .css-truncate-target{max-width:500px}.deleted-branch-summary .branch-name{text-decoration:line-through;opacity:0.5}.deleted-branch-summary .branch-spinner{position:relative;top:4px;right:5px;float:right}.pr-details{display:inline-block;width:144px;text-align:right}.pr-details .state{width:75px;padding:1px 5px;margin-left:5px;font-size:12px;text-decoration:none}.branch-delete{display:inline-block;margin:4px 2px 0 8px;color:#bd2c00}.branch-delete.disabled{color:#ddd}.more-branches{display:block;width:100%;padding:6px;color:#4078c0;text-align:center;text-decoration:none;background:#f1f7fa;border:1px solid #dae5eb;border-radius:0 0 3px 3px}.more-branches:hover{background:#e6f1f6}.more-branches .octicon{position:relative;top:1px;margin-left:5px}.branch-details{display:inline-block;width:490px;margin-right:10px}.branch-details .css-truncate-target{max-width:240px}.branch-details .octicon-shield{margin-right:2px}.branch-meta{font-size:12px;line-height:20px;color:#aaa}.default-label{display:inline-block;width:150px;text-align:center;vertical-align:top}.default-label .sha{font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace}.default-label .sha .ellipses{font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";color:inherit}.default-label .sha .octicon{padding-right:4px}.branch-a-b-count{display:inline-block;vertical-align:middle}.branch-a-b-count .count-half{position:relative;float:left;width:90px;padding-bottom:6px;text-align:right}.branch-a-b-count .count-half:last-child{text-align:left;border-left:1px solid #bbb}.branch-a-b-count .count-value{position:relative;top:-1px;display:block;padding:0 3px;font-size:10px}.branch-a-b-count .bar{position:absolute;min-width:3px;height:4px}.branch-a-b-count .meter{position:absolute;height:4px;background-color:#ccc}.branch-a-b-count .meter.zero{background-color:transparent}.branch-a-b-count .bar-behind{right:0;border-radius:3px 0 0 3px}.branch-a-b-count .bar-behind .meter{right:0;border-radius:3px 0 0 3px}.branch-a-b-count .bar-ahead{left:0;border-radius:0 3px 3px 0}.branch-a-b-count .bar-ahead .meter{border-radius:0 3px 3px 0}.branch-a-b-count .bar-ahead.even,.branch-a-b-count .bar-behind.even{min-width:2px;background:#eee}.code-frequency .addition{fill:#6cc644;fill-opacity:1}.code-frequency .deletion{fill:#bd2c00;fill-opacity:1}.cadd{font-weight:bold;color:#6cc644}.cdel{font-weight:bold;color:#bd2c00}.codesearch-head{padding-bottom:20px}.codesearch-head.pagehead h1{float:left;width:250px;line-height:33px}.advanced-search-form h3{margin-top:20px}.advanced-search-form .flattened dt{width:230px}.advanced-search-form .flattened dt label{font-weight:normal}.advanced-search-form .flattened dd{margin-left:250px}.advanced-search-form .form-checkbox{margin-left:250px}.advanced-search-form fieldset{padding-bottom:20px;margin-bottom:30px;border-bottom:1px solid #f1f1f1}.codesearch-results .repo-list{margin-top:-20px}.codesearch-results .repo-list-name{font-weight:normal}.codesearch-results .repo-list-name .octicon{vertical-align:middle}.codesearch-results .repo-list-name a,.codesearch-results .code-list .title a{word-wrap:break-word}.codesearch-results .repo-list-name em,.codesearch-results .repo-list-description em{padding:3px;font-style:normal;font-weight:bold;background-color:rgba(255,255,140,0.5);border-radius:3px}.codesearch-results .sort-prefix{font-style:normal;font-weight:500;opacity:0.6}.meta-search-links{margin-top:20px}.meta-search-links a{margin-right:10px}.codesearch-aside .menu .octicon{width:16px;margin-right:5px;text-align:center}.codesearch-aside .meta-search-links{margin-top:20px}.codesearch-aside .meta-search-links a{margin-right:10px}.codesearch-aside .filter-list{padding-bottom:20px;margin-bottom:20px;border-bottom:1px solid #f1f1f1}.codesearch-aside .filter-list li{position:relative}.codesearch-aside .filter-list li span.bar{position:absolute;top:2px;right:0;bottom:2px;z-index:-1;display:inline-block;background:#f1f1f1}.simple-search-page{padding-top:100px;padding-bottom:100px}.search-form-fluid .flex-table-item-primary{position:relative;padding-right:6px}.search-form-fluid .completed-query{position:absolute;top:7px;right:8px;left:8px;z-index:1;margin:0;overflow:hidden;white-space:nowrap}.search-form-fluid .completed-query span{opacity:0}.search-form-fluid .search-page-label{position:relative;display:block;font-weight:normal;cursor:text}.search-form-fluid .search-page-label.focus .completed-query{opacity:0.6}.search-form-fluid .search-page-input{position:relative;z-index:2;min-height:0;padding:0;margin:0;background:none;border:0;box-shadow:none}.search-form-fluid .search-page-input:focus{box-shadow:none}.token-warning{position:absolute;top:10px;right:18px;color:#000}.sort-bar{padding-bottom:20px;margin-bottom:20px;border-bottom:1px solid #f1f1f1}.sort-bar .sort-label{padding-right:5px;font-size:13px;font-weight:200;color:#666}.sort-bar .select-menu{float:right}.facebox .markdown-body .octicon{vertical-align:inherit}.commit-activity-graphs .dots{display:none}.commit-activity-master{margin-top:20px}.is-graph-loading .commit-activity-master{display:none}rect{shape-rendering:crispedges}rect.max{fill:#ffc644}g.bar{fill:#1db34f}g.mini{fill:#f17f49}g.active rect{fill:#bd380f}circle.focus{fill:#555}.dot text{fill:#555;stroke:none}.compare-cutoff,.diff-cutoff{padding:8px 0;margin:5px 0;font-weight:bold;color:#4c4a42;text-align:center;background-color:#fff9ea;border:solid 1px #dfd8c2;border-radius:3px}span.no-nl-marker{position:relative;color:#bd2c00;vertical-align:middle}.symlink .no-nl-marker{display:none}.existing-pull{margin:10px 0}.existing-pull .list-group-item::before{display:table;content:""}.existing-pull .list-group-item::after{display:table;clear:both;content:""}.existing-pull .existing-pull-contents{float:left;width:680px}.existing-pull .existing-pull-button{float:right;margin-top:3px}.existing-pull .existing-pull-number{font-weight:normal;color:#aaa}.existing-pull .css-truncate{max-width:700px}.existing-pull .css-truncate p{display:inline}.compare-pr-placeholder{padding:15px;margin:15px 0;font-size:14px;color:#4c4a42;background-color:#fff9ea;border:solid 1px #dfd8c2;border-radius:3px}.compare-pr-placeholder p{margin:7px 0;color:#6d6c60}.compare-pr-placeholder .btn{margin-right:10px;margin-bottom:-2px}.compare-pr-placeholder .help-link{padding:3px;margin-top:5px;margin-right:-3px;color:#9c997d;text-decoration:none}.compare-pr .new-pr-form{display:none}.compare-pr .contributing{display:none}.compare-pr.open .compare-pr-placeholder{display:none}.compare-pr.open .new-pr-form{display:block}.compare-pr.open .contributing{display:block}.contact-github textarea{height:100px;resize:vertical}.contact-github .contact-checklist>li{margin:5px 0 5px 18px;list-style-position:outside}.documentation-results-wrapper{position:relative;top:-19px}.documentation-results{position:absolute;top:0;z-index:2;width:400px;margin-top:5px;box-shadow:0 0 5px rgba(0,0,0,0.2)}.documentation-results ul{width:100%}.documentation-results ul li:first-child a{border-top-left-radius:3px;border-top-right-radius:3px}.documentation-results .documentation-results-footer a{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.documentation-results a{display:block;padding:5px 10px;font-weight:bold;color:#333;text-decoration:none;cursor:pointer;background-color:#fff;border:solid #ddd;border-width:0 1px 1px;outline:none}.documentation-results a:hover{color:#fff;background-color:#3586c3}.documentation-results a.selected{color:#fff;background-color:#3586c3}ul.documentation-results-group{list-style-type:none}.contact-form-extras{display:none}.overview-tab{margin-top:20px}.overview-tab .simple-conversation-list .state{margin-top:3px}.select-menu-item{text-align:left;background-color:#fff;border-top:0;border-right:0;border-left:0}.contributions-setting .contributions-setting-link{font-size:13px}.contributions-setting .select-menu-modal{width:330px}.contributions-setting .select-menu-item-text{font-weight:normal}.calendar-graph{height:126px;padding:5px 0 0;text-align:center}.calendar-graph.days-selected rect.day{opacity:0.5}.calendar-graph.days-selected rect.day.active{opacity:1}.calendar-graph .dots{width:64px;height:64px;margin:20px auto 0}.calendar-graph text.month{font-size:10px;fill:#aaa}.calendar-graph text.wday{font-size:9px;fill:#ccc}.contributions-calendar rect.day{shape-rendering:crispedges}.contributions-calendar rect.day.empty:hover{stroke:none}.contributions-calendar rect.day:hover{stroke:#555;stroke-width:1px}.contrib-footer{padding:0 10px 12px;font-size:11px}.contrib-legend{float:right}.contrib-legend .legend{position:relative;bottom:-1px;display:inline-block;margin:0 5px;list-style:none}.contrib-legend .legend li{display:inline-block;width:10px;height:10px}.new-user-contrib-intro{padding:5px 20px;font-size:16px;border-top:solid 1px #ddd}.contrib-square{font-size:22px;line-height:1;color:#d6e685}.contribution-activity h2{margin:30px 0 15px;font-size:18px;font-weight:normal}.contribution-activity .select-menu-button{position:relative;top:-4px}.contribution-activity.loading .contribution-activity-listing{display:none}.contribution-activity.loading .contribution-activity-spinner{display:block}.contribution-activity-spinner{display:none;width:64px;height:64px;margin:20px auto 0}ul.simple-conversation-list a.meta{color:#767676}li.contribution{padding:10px 0;list-style:none}li.contribution:last-child{border-bottom:0}li.contribution h3{display:inline-block;margin:0;font-size:14px}li.contribution .cmeta{display:block;font-size:12px}li.contribution .cmt{color:#767676}li.contribution .d{color:#c00}li.contribution .a{color:#8cac29}li.contribution .num{color:#767676}.tint-box{position:relative;margin-bottom:10px;background:#f3f3f3;border-radius:6px}.tint-box.transparent{background:#fff}.tint-box .activity{padding-top:100px;margin-top:0}.contrib-data{padding:0;margin:0 0 10px;list-style:none}.contributors-graph .capped-card .avatar{float:left;width:32px;height:32px;margin-right:5px}.contributors-graph .capped-card h3{font-weight:normal}.contributors-graph .capped-card .ameta{display:block;font-size:12px;color:#ccc}.contributors-graph .capped-card .rank{float:right;font-size:13px;color:#767676}.contributors-graph .capped-card .cmt{color:#767676}.contributors-graph .capped-card path{fill:#f17f49}.contributors-graph .capped-card .midlabel{fill:#ccc}.d{color:#bd2c00}.a{color:#6cc644}.logged-out.enter-coupon{background-color:#f9f9f9}.logged-out.enter-coupon .coupon-form-body{margin-bottom:-20px;background-image:none;box-shadow:0 1px 3px rgba(0,0,0,0.075),inset 1px 0 #fff,0 0 200px #fff}.logged-out.enter-coupon .header-logged-out{background-color:#fff}.logged-out.enter-coupon .site-footer{border-top:0}.coupons .setup-plans td img{margin-top:-2px;vertical-align:middle}.coupons .coupon-form-body{width:230px;padding:20px;margin:100px auto 60px;font-size:14px;text-align:center;background-color:#fff;background-image:-webkit-linear-gradient(#fefefe, #fafafa);background-image:linear-gradient(#fefefe, #fafafa);border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.075),inset 1px 0 #fff}.coupons .coupon-form-body .input-block{margin-bottom:15px}.coupons .coupon-form-body .btn{display:block;width:100%}.coupon-icon{width:80px;height:80px;margin:0 auto 15px;color:#4078c0;border:1px solid #dedede;border-radius:40px}.coupon-icon .octicon{margin-top:15px;margin-right:2px}.coupon-signin-title{margin-top:40px}.coupon-title{margin-bottom:20px;font-weight:500}.coupons-list-options{margin-bottom:15px}.coupons-list-options .select-menu,.coupons-list-options .btn-group{display:inline-block;margin-right:10px}.coupons-list-options .pagination{float:right;margin:0}.subscribe-feed{color:#333}.subscribe-feed .octicon{margin-right:5px}.user-repos .mini-repo-list-item{padding-right:6px}.user-repos .mini-repo-list-item .repo-and-owner{max-width:100%}.user-repos .mini-repo-list-item .owner{max-width:145px}.example-octofication{float:right;width:335px;margin:0}.octofication{margin-bottom:15px}.octofication .message{min-height:56px;padding:10px 10px 10px 50px;border:solid 1px #4078c0;border-radius:3px}.octofication .message h3{margin:1px 20px 3px 0;font-size:14px;line-height:1.2}.octofication .message p{padding:0;margin:0;font-size:12px;color:#555}.octofication .message p+p{margin-top:15px}.octofication .broadcast-icon{position:relative;float:left;margin-left:-40px;color:#4078c0}.octofication .broadcast-icon-mask{position:absolute;top:0;width:10px;height:16px;background-color:#fff;opacity:0;-webkit-animation:broadCastMaskFade 1s ease-in-out 2s 2;animation:broadCastMaskFade 1s ease-in-out 2s 2}.octofication .broadcast-icon-mask.left{left:0}.octofication .broadcast-icon-mask.right{right:0}.octofication .notice-dismiss{position:relative;top:-2px;float:right;color:#bbb}.octofication .notice-dismiss:hover{color:#666}.octofication-more{margin:5px 0;font-size:11px;text-align:right}@-webkit-keyframes broadCastMaskFade{0%{opacity:0}30%{opacity:1}70%{opacity:1}100%{opacity:0}}@keyframes broadCastMaskFade{0%{opacity:0}30%{opacity:1}70%{opacity:1}100%{opacity:0}}.github-jobs-promotion{margin-bottom:15px}.github-jobs-promotion p{position:relative;padding:10px 18px;font-size:12px;color:#1b3650;text-align:center;background-image:-webkit-linear-gradient(#f5fbff, #e4f0ff);background-image:linear-gradient(#f5fbff, #e4f0ff);border:1px solid #cee0e7;border-radius:3px}.github-jobs-promotion p a{color:#1b3650}.github-jobs-promotion a.jobs-logo{display:block;text-align:center}.github-jobs-promotion a.jobs-logo:hover{text-decoration:none}.github-jobs-promotion a.jobs-logo strong{display:inline-block;width:62px;height:12px;text-indent:-9999px;vertical-align:middle;background:url("/images/modules/jobs/logo.png") 0 0 no-repeat;background-size:62px auto}.github-jobs-promotion .job-location{white-space:nowrap}.github-jobs-promotion a.octicon-info{position:absolute;right:5px;bottom:5px;color:#a9b8be;text-decoration:none;cursor:pointer;opacity:0.8}.github-jobs-promotion p:hover .octicon-info{opacity:1}.dashboard h1{margin-bottom:0.5em;font-size:160%}.dashboard h1 a{font-size:70%;font-weight:normal}.dashboard .notice{padding:15px;margin-top:0;margin-bottom:0;text-align:center}.release-assets{padding-left:40px}.release-assets li{margin-top:0.15em;list-style-type:none}.release-assets .more{padding-top:2px;font-size:11px}.news-full,.page-profile .news{float:none;width:auto}.activity-tab .blankslate{margin-top:10px}.activity-tab .news .markdown-body blockquote,.activity-tab .news .alert .commits{padding-left:0}.activity-tab .news a.gravatar,.activity-tab .news div.gravatar{display:none}.saml-signed-out-notice{position:relative;width:450px;padding:10px 10px 10px 70px;margin:50px auto 30px;border:1px solid #eee;border-radius:3px}.saml-signed-out-notice .octicon{position:absolute;top:30px;left:20px;color:#ddd}.saml-signed-out-notice h3{margin-bottom:0}.saml-signed-out-notice p{margin-top:5px}.survey-box.simple-box{position:fixed;right:25px;bottom:-20px;z-index:25;max-width:400px;padding-bottom:0;background-color:#f9f9f9;border-bottom:0;border-radius:4px 4px 0 0;box-shadow:0 0 10px rgba(0,0,0,0.05);-webkit-transform:translate3d(0, 0, 0);transform:translate3d(0, 0, 0)}.survey-box.simple-box .simple-box-title{padding-bottom:0;margin-bottom:-7px;font-size:14px;line-height:1.3;border-bottom:0}.survey-box.simple-box .simple-box-footer{padding:7px;margin:0 -15px;background-color:#fff}.survey-box.simple-box .close-button{margin-top:-5px;color:#aaa}.newsfeed-placeholder{padding:30px;border:2px dashed #ddd;border-radius:5px}.newsfeed-placeholder h3{margin-bottom:10px;line-height:1.2}.newsfeed-placeholder .btn-outline{margin-top:10px}.newsfeed-placeholder-graphic{float:left;margin-right:40px}.newsfeed-footer{margin-left:45px}.newsfeed-footer .newsfeed-tip{text-align:left}.dashboard-notice{position:relative;padding:15px 15px 15px 55px;margin-bottom:20px;font-size:14px;background-color:#fafafa;border:solid 1px #d8d8d8;border-radius:3px}.dashboard-notice .dismiss{position:absolute;top:10px;right:10px;width:16px;height:16px;color:#bbb;cursor:pointer}.dashboard-notice .dismiss:hover{color:#666}.dashboard-notice .notice-icon{position:absolute;top:15px;left:15px}.dashboard-notice .octicon-organization{color:#4078c0}.dashboard-notice h2{margin-top:9px;margin-bottom:16px;font-size:18px;font-weight:normal;color:#000}.dashboard-notice p.no-title{padding-right:5px}.dashboard-notice .inset-figure{float:right;margin-bottom:15px;margin-left:20px}.dashboard-notice ul{margin-left:18px}.dashboard-notice li{padding-bottom:15px}.dashboard-notice .coupon{padding:10px;margin:15px 0;font-size:20px;font-weight:bold;text-align:center;background:#fff;border:1px dashed #d1e5ff}.dashboards-overview-lead{width:700px}.dashboards-overview-cards .boxed-group{width:100%;margin:10px 0}.dashboards-overview-cards .boxed-group .graph-canvas path{stroke:#1db34f;stroke-opacity:0.5}.dashboards-overview-cards .is-no-activity .blankslate{display:block}.dashboards-overview-cards .is-no-activity .dashboards-overview-graph{display:none}.dashboards-overview-cards .blankslate{display:none;padding-top:47px;background-color:#fff;border:0;box-shadow:none}.dashboards-overview-cards .octicon-arrow-down,.dashboards-overview-cards .octicon-arrow-up{display:none}.dashboards-overview-cards .is-increase .octicon-arrow-up{display:inline-block}.dashboards-overview-cards .is-decrease .octicon-arrow-down{display:inline-block}.dashboards-overview-cards .octicon-arrow-down{color:#bd2c00}.dashboards-overview-cards .octicon-arrow-up{color:#1db34f}.dashboards-overview-cards .graph-canvas .dots{padding:43px 0}.dashboards-overview-cards .summary-stats{height:78px}.dashboards-overview-cards .summary-stats .created_at{color:#1db34f}.dashboards-overview-cards .summary-stats .closed_at,.dashboards-overview-cards .summary-stats .merged_at{color:#4078c0}.dashboards-overview-cards .summary-stats .totals-num{margin:0 7px}.dashboards-overview-cards .summary-stats .single{width:100%}.dashboards-overview-graph{height:160px}.dashboards-overview-graph .path{fill:none;stroke-width:2}.dashboards-overview-graph path.created_at{stroke:#1db34f}.dashboards-overview-graph path.merged_at,.dashboards-overview-graph path.closed_at{stroke:#1d7fb3}.dashboards-overview-graph .y line{stroke:#1db34f}.dashboards-overview-graph .y.unique line{stroke:#1d7fb3}.dashboards-overview-graph .overlay{fill-opacity:0}.created_at circle{fill:#1db34f;stroke:#fff;stroke-width:2}.merged_at circle,.closed_at circle{fill:#1d7fb3;stroke:#fff;stroke-width:2}dl.form.developer-select-account{margin-top:0}.developer-wrapper .setup-info-module .features-list{margin-left:16px}.developer-wrapper .setup-info-module .features-list .octicon{margin-left:-17px}.developer-thanks h2{font-size:38px;font-weight:normal}.developer-thanks .hook{margin-top:2px;margin-bottom:30px;font-size:18px;font-weight:300;color:#666}.developer-thanks-image{position:relative;bottom:-45px;float:left;width:400px}.developer-thanks-section{margin:130px 0 0 470px}.developer-next-steps{font-size:18px;font-weight:300;list-style:none}.developer-next-steps li{margin-top:10px}.developer-next-steps li:first-child{margin-top:0}.developer-next-steps .octicon{margin-right:10px;color:#6cc644;vertical-align:middle}.discussion-timeline{position:relative;float:left;width:760px}.discussion-timeline::before{position:absolute;top:0;bottom:0;left:79px;z-index:-1;display:block;width:2px;content:"";background-color:#f3f3f3}.discussion-timeline .email-hidden-container{margin:3px 0}.discussion-sidebar{position:-webkit-sticky;position:sticky;top:0;z-index:21;float:right;width:200px}.discussion-sidebar-item{padding-top:15px;font-size:12px;color:#767676}.discussion-sidebar-item .btn .octicon{margin-right:0}.discussion-sidebar-item .btn-block{margin-bottom:8px}.discussion-sidebar-item+.discussion-sidebar-item{margin-top:15px;border-top:1px solid #eee}.discussion-sidebar-item .select-menu{position:relative}.discussion-sidebar-item .select-menu-modal-holder{top:25px;right:-1px;left:auto}.discussion-sidebar-heading{margin-bottom:10px;font-size:12px;line-height:16px;color:#767676}.discussion-sidebar-toggle{padding:5px 0;margin:-5px 0 5px}.discussion-sidebar-toggle .octicon{float:right;color:#ccc}.discussion-sidebar-toggle:hover{color:#4078c0;text-decoration:none;cursor:pointer}.discussion-sidebar-toggle:hover .octicon{color:inherit}button.discussion-sidebar-toggle{display:block;width:100%;font-weight:bold;text-align:left;background:none;border:0}.sidebar-labels .labels .label{display:block;max-width:100%;padding:6px 10px;font-size:12px;box-shadow:none}.sidebar-labels .labels .label+.label{margin-top:3px}.sidebar-milestone .progress-bar{height:8px;margin-bottom:2px;border-radius:2px}.milestone-name{display:block;margin-top:5px;font-weight:bold;color:#555}.milestone-name .css-truncate-target{max-width:100%}.milestone-name:hover{color:#4078c0;text-decoration:none}.sidebar-assignee .css-truncate-target{max-width:110px}.sidebar-assignee .assignee{font-weight:bold;color:#555;vertical-align:middle}.sidebar-assignee .assignee:hover{color:#4078c0;text-decoration:none}.sidebar-notifications{position:relative}.sidebar-notifications .thread-subscription-status{padding:0;margin:0;border:0}.sidebar-notifications .thread-subscription-status .thread-subscribe-form{display:block}.sidebar-notifications .thread-subscription-status .octicon-radio-tower{display:none}.sidebar-notifications .thread-subscription-status .reason{padding:0;margin:5px 0 0}.sidebar-notifications .thread-subscription-status .btn-sm{display:block;width:100%}.participation .participant-avatar{float:left;margin:3px 0 0 3px}.participation a{color:#767676}.participation a:hover{color:#4078c0;text-decoration:none}.participation-avatars{margin-left:-3px}.participation-avatars::before{display:table;content:""}.participation-avatars::after{display:table;clear:both;content:""}.participation-more{float:left;margin:6px 0 0;line-height:14px}.lock-toggle-link{font-weight:bold;color:#767676}.lock-toggle-link:hover{color:#4078c0;text-decoration:none}.inline-comment-form .form-actions,.timeline-new-comment .form-actions{padding:0 10px 10px}.inline-comment-form::before{display:table;content:""}.inline-comment-form::after{display:table;clear:both;content:""}.inline-comment-form .tabnav-tabs{display:inline-block}.inline-comment-form .form-actions{float:right}.gh-header-actions{float:right;margin-top:3px}.gh-header-actions .btn-sm{float:left;margin-left:5px}.gh-header-actions .btn-sm .octicon{margin-right:0}.gh-header .gh-header-edit{display:none}.gh-header.open .gh-header-show{display:none}.gh-header.open .gh-header-edit{display:block}.gh-header-title{margin-right:150px;margin-bottom:0;font-weight:normal;line-height:1.1;word-wrap:break-word}.gh-header-no-access .gh-header-title{margin-right:0}.gh-header-number{font-weight:300;color:#aaa;letter-spacing:-1px}.gh-header-edit{margin-top:-5px}.gh-header-edit::before{display:table;content:""}.gh-header-edit::after{display:table;clear:both;content:""}.gh-header-edit .edit-issue-title{float:left;width:760px;padding:6px 10px;margin-right:10px;font-size:16px;background-color:#fafafa}.gh-header-edit .edit-issue-title:focus{background-color:#fff}.gh-header-edit .btn{float:left;padding:7px 15px}.gh-header-edit .btn-link{float:left;margin:9px 10px}.gh-header-meta{padding-bottom:20px;margin-top:9px;font-size:14px;line-height:20px;color:#767676;border-bottom:1px solid #eee}.gh-header.issue .gh-header-meta{margin-bottom:15px}.gh-header.pull .gh-header-meta{padding-bottom:0;border-bottom:0}.gh-header-meta .flex-table-item{vertical-align:top}.gh-header-meta .flex-table-item-primary{padding-top:4px;word-wrap:break-word;white-space:normal}.gh-header-meta .flex-table-item-primary .commit-ref .css-truncate-target,.gh-header-meta .flex-table-item-primary .commit-ref:hover .css-truncate-target{max-width:780px !important}.gh-header-meta .state{margin-right:8px}.gh-header-meta .avatar{float:left;margin-top:-3px;margin-right:5px}.gh-header-meta .author{font-weight:bold;color:#555}.gh-header-meta .noun{text-transform:lowercase}.tabnav-pr{margin:15px 0 20px;border-color:#e5e5e5}.tabnav-pr .tabnav-tab{position:relative;padding:9px 14px;font-size:13px;color:#767676}.tabnav-pr .tabnav-tab.selected{color:#333;border-color:#e5e5e5}.timeline-comment-wrapper>.timeline-comment::after,.timeline-comment-wrapper>.timeline-comment::before,.timeline-new-comment .timeline-comment::after,.timeline-new-comment .timeline-comment::before{position:absolute;top:11px;right:100%;left:-16px;display:block;width:0;height:0;pointer-events:none;content:" ";border-color:transparent;border-style:solid solid outset}.timeline-comment-wrapper>.timeline-comment::after,.timeline-new-comment .timeline-comment::after{margin-top:1px;margin-left:2px;border-width:7px;border-right-color:#f7f7f7}.timeline-comment-wrapper>.timeline-comment::before,.timeline-new-comment .timeline-comment::before{border-width:8px;border-right-color:#ddd}.timeline-comment-wrapper{position:relative;padding-left:64px;margin-top:15px;margin-bottom:15px;border-top:2px solid #fff;border-bottom:2px solid #fff}.timeline-comment-wrapper:first-child{margin-top:0}.discussion-timeline-actions .timeline-comment-wrapper:first-child{margin-top:15px}.timeline-comment-wrapper .timeline-comment.current-user::after,.timeline-comment-wrapper .timeline-comment.current-user::before{position:absolute;top:11px;right:100%;left:-16px;display:block;width:0;height:0;pointer-events:none;content:" ";border-color:transparent;border-style:solid solid outset}.timeline-comment-wrapper .timeline-comment.current-user::after{margin-top:1px;margin-left:2px;border-width:7px;border-right-color:#f2f8fa}.timeline-comment-wrapper .timeline-comment.current-user::before{border-width:8px;border-right-color:#bfccd1}.timeline-comment-wrapper .timeline-comment.unread-item::after,.timeline-comment-wrapper .timeline-comment.unread-item::before{position:absolute;top:11px;right:100%;left:-16px;display:block;width:0;height:0;pointer-events:none;content:" ";border-color:transparent;border-style:solid solid outset}.timeline-comment-wrapper .timeline-comment.unread-item::after{margin-top:1px;margin-left:2px;border-width:7px;border-right-color:#fff9ea}.timeline-comment-wrapper .timeline-comment.unread-item::before{border-width:8px;border-right-color:#dfd8c2}.timeline-comment-avatar{float:left;margin-left:-64px;border-radius:3px}.timeline-comment{position:relative;background-color:#fff;border:1px solid #ddd;border-radius:3px}.timeline-comment.will-transition-once{-webkit-transition:border-color 0.65s ease-in-out;transition:border-color 0.65s ease-in-out}.timeline-comment.will-transition-once .timeline-comment-header{-webkit-transition:background-color 0.65s ease, border-bottom-color 0.65s ease-in-out;transition:background-color 0.65s ease, border-bottom-color 0.65s ease-in-out}.timeline-comment.will-transition-once .timeline-comment-label{-webkit-transition:border-color 0.65s ease-in-out;transition:border-color 0.65s ease-in-out}.timeline-comment.will-transition-once::before,.timeline-comment.will-transition-once::after{-webkit-transition:border-right-color 0.65s ease-in-out;transition:border-right-color 0.65s ease-in-out}.timeline-comment.current-user{border-color:#bfccd1}.timeline-comment.current-user .timeline-comment-header{background-color:#f2f8fa;border-bottom-color:#bfccd1}.timeline-comment.current-user .timeline-comment-label{border-color:#bfccd1}.timeline-comment.current-user .previewable-comment-form .comment-form-head.tabnav{color:#8e9597;background-color:#f2f8fa;border-bottom-color:#e1edf1}.timeline-comment.unread-item{border-color:#dfd8c2}.timeline-comment.unread-item .timeline-comment-header{background-color:#fff9ea;border-bottom-color:#dfd8c2}.timeline-comment.unread-item .timeline-comment-label{border-color:#dfd8c2}.timeline-comment.unread-item .previewable-comment-form .comment-form-head.tabnav{color:#8e9597;background-color:#f2f8fa;border-bottom-color:#e1edf1}.timeline-comment:empty{display:none}.timeline-comment .comment+.comment{border-top:1px solid #e5e5e5}.timeline-comment .comment+.comment::before,.timeline-comment .comment+.comment::after{display:none}.timeline-comment .comment+.comment .timeline-comment-header{border-top-left-radius:0;border-top-right-radius:0}.timeline-comment-header{padding-right:15px;padding-left:15px;color:#767676;background-color:#f7f7f7;border-bottom:1px solid #eee;border-top-left-radius:3px;border-top-right-radius:3px}.timeline-comment-header .author{color:#555}.timeline-comment-header .timestamp{color:inherit;white-space:nowrap}.timeline-comment-header .timestamp.timestamp-edited{cursor:default}.timeline-comment-header code{word-break:break-all}.comment-type-icon{color:inherit}.timeline-comment-label{float:right;padding:2px 5px;margin:8px 0 0 10px;font-size:12px;cursor:default;border:1px solid rgba(0,0,0,0.1);border-radius:3px}.timeline-comment-label-spammy{color:#bd2c00;border-color:#bd2c00}.timeline-comment-header-text{max-width:78%;padding-top:10px;padding-bottom:10px}.timeline-comment-header-text code a{color:#555}.timeline-comment-header-avatar{float:left;margin-top:10px;margin-right:5px}.timeline-comment-actions{float:right;margin-right:-5px;margin-left:10px}.timeline-comment-action{display:inline-block;padding:10px 5px;color:inherit;opacity:0.5}.timeline-comment-action:hover,.timeline-comment-action:focus{color:#4078c0;text-decoration:none;opacity:1}.timeline-comment-action .octicon-check{height:16px;font-size:18px}.timeline-comment-action.disabled{color:#bbb;cursor:default}.timeline-comment-action.disabled:hover{color:#bbb}.compare-tab-comments .timeline-comment-actions{display:none}.discussion-item-ref .commit-gravatar{padding-right:5px;padding-left:2px}.discussion-item-ref .task-progress{display:block;margin-bottom:-2px}.discussion-item-ref .task-progress .progress-bar{margin-bottom:0}.discussion-item-ref .task-progress .octicon{font-size:16px}.discussion-item-ref .discussion-item-body .title{margin-top:10px}.discussion-item-ref .state{padding:1px 5px;margin-top:-4px;margin-left:8px;font-size:12px}.discussion-item-ref .state .octicon{width:1em;font-size:14px}.timeline-new-comment{max-width:780px;margin-bottom:0}.timeline-new-comment .comment-form-head{margin-bottom:10px}.timeline-new-comment .previewable-comment-form .comment-body{padding:5px 5px 15px;border-bottom:1px solid #eee}.discussion-item{position:relative;padding-left:25px;margin:15px 0 15px 79px}.discussion-item+.discussion-item{padding-top:15px;border-top:1px solid #f5f5f5}.discussion-item .author{font-weight:bold;color:#555}.discussion-item .timestamp{color:inherit;white-space:nowrap}.discussion-item .label-color{padding:2px 4px;font-size:12px;font-weight:bold;border-radius:2px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.12)}.discussion-item .label-color a:hover{text-decoration:none}.discussion-item.open .discussion-item-details{display:block}.discussion-item.open .discussion-item-toggler-opened{display:inline}.discussion-item.open .discussion-item-toggler-closed{display:none}.discussion-item-details{display:none}.discussion-item-deployed{padding-top:15px;border-top:1px solid #f5f5f5}.discussion-item-toggler-opened{display:none}.discussion-item-icon{float:left;width:32px;height:32px;margin-top:-7px;margin-left:-40px;line-height:28px;color:#767676;text-align:center;background-color:#f3f3f3;border:2px solid #fff;border-radius:50%}.discussion-item-icon .octicon{vertical-align:text-top}.discussion-item-icon .octicon-pencil{font-size:14px}.discussion-item-header{min-height:30px;padding-top:5px;padding-bottom:5px;line-height:20px;color:#767676;word-wrap:break-word}.discussion-item-header .avatar{float:left;margin-top:2px;margin-right:5px}.discussion-item-header .discussion-item-private{vertical-align:-1px}.discussion-item-header:last-child{padding-bottom:0}.discussion-item-header .commit-ref{font-size:85%;vertical-align:baseline}.discussion-item-header .btn-outline{float:right;padding:4px 8px;margin-top:-5px;margin-left:10px}.discussion-item-body{margin-top:5px}.discussion-item-footer{padding-left:21px;font-size:12px}.discussion-item-link{color:#767676}.discussion-item-link:hover{color:#4078c0}.discussion-item-entity{font-weight:bold;color:#333}.discussion-item-entity:hover{color:#4078c0;text-decoration:none}.discussion-item-ref-title .issue-num{font-weight:normal;color:#767676}.discussion-item-ref-title .title-link{color:#333}.discussion-item-ref-title .title-link:hover{color:#4078c0;text-decoration:none}.discussion-item-ref-title .title-link:hover .issue-num{color:inherit}.discussion-item-context-icon{display:inline-block;margin-top:-2px;margin-left:10px;line-height:22px}.discussion-item-help{color:#767676}.discussion-item-help:hover{color:#4078c0}.discussion-item-private{color:#4c4a42}.discussion-item-rollup-ref .state{margin-top:2px}.discussion-item-rollup-ref .discussion-item-context-icon{margin-top:2px}.discussion-item-reopened .discussion-item-icon,.discussion-item-review.is-approved .discussion-item-icon{color:#fff;background-color:#6cc644}.discussion-item-closed .discussion-item-icon,.discussion-item-review.is-rejected .discussion-item-icon{color:#fff;background-color:#bd2c00}.discussion-item-head_ref_deleted .discussion-item-icon{padding-left:1px;color:#fff;background-color:#767676}.discussion-item-locked .discussion-item-icon,.discussion-item-unlocked .discussion-item-icon{color:#fff;background-color:#333}.discussion-item-integrations-callout .discussion-item-icon{color:#fff;background-color:#4095c6}.discussion-item-integrations-callout .pull-request-integrations-dismiss{padding:4px 8px;margin:-3px 0 0 10px;color:#767676}.pull-request-integrations-title{margin:0;font-size:15px;color:#333}.pull-request-integrations-body{margin-top:5px;color:#666}.discussion-item .renamed-was,.discussion-item .renamed-is{font-weight:bold;color:#333}.discussion-commits .discussion-item-icon{padding-top:1px}.discussion-commits .discussion-item-body{margin-top:0;margin-left:-31px}.discussion-item-changes-marker{margin-bottom:0}.discussion-item-changes-marker.is-unread .discussion-item-icon{color:#fff;background-color:#4078c0}.discussion-item-changes-marker+.discussion-commits{padding-top:0;margin-top:0;border-top:0}.discussion-item-changes-marker+.discussion-commits .discussion-item-icon{display:none}.discussion-item-toggle-open{display:none}.discussion-item-toggle{float:right;color:#767676}.discussion-item-toggle:hover{color:#4078c0;text-decoration:none}.outdated-diff-comment-container .discussion-item-body{display:none}.outdated-diff-comment-container.open .discussion-item-body,.outdated-diff-comment-container.open .discussion-item-toggle-open{display:block}.outdated-diff-comment-container.open .discussion-item-toggle-closed{display:none}.new-discussion-timeline .previewable-comment-form .comment-form-head.tabnav{padding:6px 10px 0;background:#f7f7f7;border-radius:3px 3px 0 0}.new-discussion-timeline .previewable-comment-form .draft-indicator{position:relative;top:-1px}.new-discussion-timeline .previewable-comment-form .comment{border:0}.new-discussion-timeline .previewable-comment-form .comment-body{padding:5px 5px 15px;background-color:transparent;border-bottom:1px solid #eee}.new-discussion-timeline .previewable-comment-form .timeline-comment .timeline-comment-actions{display:none}.new-discussion-timeline .closed-banner{position:relative;height:19px;margin:15px 0 -15px;overflow:visible;background:#f3f3f3;border-bottom:15px solid #fff;border-radius:0}.new-discussion-timeline .composer .timeline-comment{margin-bottom:10px}.new-discussion-timeline .composer .timeline-comment::after{border-right-color:#fff}.new-discussion-timeline .composer .comment-form-head.tabnav{padding-top:0;background-color:#fff}.discussion-timeline-actions{background-color:#fff;border-top:2px solid #f3f3f3}.discussion-timeline-actions .merge-pr{padding-top:0;border-top:0}.discussion-timeline-actions .thread-subscription-status{margin-top:20px}.discussion-timeline-actions .thread-subscription-status .octicon-radio-tower{display:none}.discussion-item-merged .discussion-item-icon{padding-left:1px;color:#fff;background-color:#6e5494}.discussion-item-merged.open .discussion-item-footer{display:none}.discussion-item-merged.open .discussion-item-details{margin-top:5px;margin-bottom:10px;border:1px solid #ddd;border-radius:3px}.discussion-item-merged.open .discussion-item-details-header{padding:12px 15px;margin-top:0;margin-bottom:0;font-size:inherit;border-top:1px solid #ddd}.discussion-item-merged.open .discussion-item-details-header:first-child{border-top:0}.discussion-item-merged.open .build-statuses-list{max-height:370px;margin:0;border-top-color:#ddd}.discussion-item-merged.open .build-status-item{padding-left:15px}.timeline-progressive-disclosure-button{display:inline-block;padding:5px 25px;text-align:center;background-color:#fff;border:solid 2px #e8e8e8;border-radius:3px}.timeline-progressive-disclosure-button:hover,.timeline-progressive-disclosure-button:focus{text-decoration:none;border-color:#4078c0}.timeline-progressive-disclosure-loader{display:none}.discussion-item+.timeline-progressive-disclosure-container,.timeline-progressive-disclosure-container+.discussion-item{padding-top:0;border-top:0}.timeline-progressive-disclosure-container{padding-left:64px;margin-bottom:15px;margin-left:0;text-align:center;background:#fff url("/images/modules/pulls/progressive-disclosure-line@2x.png") repeat-x left center/44px 17px}.timeline-progressive-disclosure-container .discussion-item-header{padding-top:0}.timeline-progressive-disclosure-container.is-loading .timeline-progressive-disclosure-loader{display:inline-block;vertical-align:bottom}.timeline-progressive-disclosure-container.is-loading .timeline-progressive-disclosure-link{display:none}.deployment-icon .octicon-rocket{position:relative;bottom:-1px;left:-1px}.environment-name{color:#767676}.deployment-meta{font-size:12px;color:#767676}.deployment-meta .octicon{color:#ccc}.deployment-status-label{display:inline-block;padding:1px 4px;margin-top:-2px;margin-right:3px;font-size:10px;line-height:16px;color:#767676;vertical-align:middle;background:none;border:solid 1px #e5e5e5;border-radius:2px}.deployment-status-label.is-error,.deployment-status-label.is-failure{color:#bd2c00;text-align:center}.deployment-status-label.is-active{color:#55a532}.deployment-status-label.is-pending{color:#cea61b}.discussion-item-review{border:1px solid #ddd;border-radius:3px}.discussion-item-review .discussion-item-body{margin-top:0}.discussion-item-review .discussion-item-body .form-actions{margin-right:10px;margin-bottom:10px}.discussion-item-review .discussion-item-body .comment-form-head.tabnav{background-color:transparent}.discussion-item-review .discussion-item-header{padding:10px;background-color:#f5f5f5;border-bottom:1px solid #ddd}.discussion-item-review .discussion-item-icon{margin-left:-50px}.discussion-item-review .file-header{padding:2px 15px;border-top:1px solid #ddd;border-bottom:1px solid #ddd;border-radius:0}.discussion-item-review.is-pending{border-color:#dfd8c2}.discussion-item-review.is-pending .file-header,.discussion-item-review.is-pending .discussion-item-header{background-color:#fff9ea;border-color:#dfd8c2}.discussion-item-review-comments{width:100%;table-layout:fixed}.discussion-item-review-comment{border-bottom:1px solid #eee}.discussion-item-review-comment:last-child{border-bottom:0}.discussion-item-review-comment-line-num{width:10%;min-width:4em;padding:12px 0 10px 15px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;text-align:right;vertical-align:top}.discussion-item-review-comment-body{width:90%}.discussion-item-review-comment-body.comment-body{padding:10px 15px}.discussion-item-review-footer{padding:10px;border-top:1px solid #eee}.donut-chart>.error,.donut-chart>.failure{fill:#bd2c00}.donut-chart>.expected,.donut-chart>.pending{fill:#cea61b}.donut-chart>.success{fill:#6cc644}.survey-question-form .other-text-form,.survey-question-form .other-text-form-block{display:none;margin-top:0}.survey-question-form.is-other-selected .other-text-form{display:inline-block}.survey-question-form.is-other-selected .other-text-form-block{display:block}.setup-header .large-file-storage-header{font-size:44px}.early-acccess-setup-form .form-group{margin-top:0;margin-bottom:30px}.early-acccess-setup-form select{display:block;width:200px}.early-access-setup-list{padding:0 15px 15px;margin:0;font-size:14px}.early-access-setup-list .early-access-setup-list-item{margin-top:10px;margin-left:20px}.early-access-setup-list .early-access-setup-list-item:first-child{margin-top:0}.early-access-thanks-wrapper{position:relative;z-index:1;height:80vh;margin-bottom:-41px;background-color:#fcfcfc;border-bottom:1px solid #ddd}.early-access-thanks-content{position:relative;top:50%;width:500px;margin:0 auto;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.early-access-thanks-content .simple-box{padding:30px;font-size:16px}.early-access-thanks-title{margin-top:0;font-weight:normal}.early-access-thanks-lead{margin-top:0;margin-bottom:0}.eap-error-state-title{margin-top:0}.ghe-license-status{padding:40px 0;font-size:16px;text-align:center}.ghe-license-status .octocat{width:225px;margin-bottom:20px}.ghe-license-status h1{margin-bottom:10px}.ghe-license-status p{margin-bottom:5px;color:#767676}.ghe-license-expiry-icon{margin:5px 10px 0 0;color:#ddb38a}.enterprise .flash-warn{max-height:105px;overflow-y:scroll}.pagehead.explore-head{background-clip:padding-box;border-bottom-color:rgba(0,0,0,0.1)}.pagehead.explore-head .container{position:relative}.explore-content{margin-top:-15px}.explore-content .blankslate{margin-top:15px}.repo-collection>ul{list-style-type:none;background:#f7f7f7;border:1px solid #ddd;border-radius:3px}.repo-collection .author-gravatar{float:left;margin-right:10px;background:#fff;border-radius:3px}.collection-stat{float:right;margin-left:10px;font-size:12px;color:#444}.collection-stat .octicon{margin-right:5px;color:#a7a7a7}.collection-item{position:relative;float:left;width:50%;height:70px;padding:15px}.collection-item .octicon-x{position:absolute;top:10px;right:10px;color:#ccc;text-decoration:none}.collection-item .repo-name{display:block;font-size:16px;font-weight:bold}.collection-item .css-truncate-target{max-width:380px}.collection-item .repo-description{margin:0}.explore-collection h2{margin:0 0 10px;font-size:18px;font-weight:normal;color:#2a2a2a}.explore-collection h2 .select-menu{position:relative;display:inline-block}.explore-collection h2 .select-menu-button{font-weight:bold;cursor:pointer}.explore-collection h2 .octicon{vertical-align:middle}.explore-page.marketing-section{border-bottom:0}.explore-page.marketing-section .thread-subscription-status{border:0}.explore-page.marketing-section .signed-out-comment{margin-left:0}.explore-page .language-filter-list{margin-bottom:10px}.explore-section{position:relative;padding:40px 0;border-bottom:1px solid #eee}.explore-section:nth-child(even){background:#f9f9f9}.explore-section:nth-child(even) .repo-collection>ul{background:#fff}.explore-section:first-child{padding-top:0}.explore-section:nth-child(odd):last-child{padding-bottom:0;border-bottom:0}.explore-pjax-container{position:relative}.user-leaderboard-list .follow-list-info{margin-top:12px;margin-bottom:0;font-size:12px;color:#666}.user-leaderboard-list .follow-list-info .css-truncate.css-truncate-target{max-width:none}.user-leaderboard-list .repo-list-item{padding-top:10px;padding-bottom:0;padding-left:21px;border-top:0}.user-leaderboard-list .repo-list-item .repo-description,.user-leaderboard-list .repo-list-item .repo-and-owner{max-width:530px}.user-leaderboard-list .repo-list-item .repo{color:#5c5c5c}.leaderboard-list{margin:0;list-style-type:none}.user-leaderboard-list-name{margin:0;font-size:18px;font-weight:normal}.user-leaderboard-list-name .full-name{margin-left:5px;font-weight:bold;color:#5c5c5c}.repo-snipit{display:inline-block;margin-top:7px}.repo-snipit:hover{text-decoration:none}.repo-snipit .octicon{font-size:14px;color:#767676}.repo-snipit-name{max-width:200px;color:#666}.repo-snipit-description{max-width:300px;color:#767676}.repo-snipit:hover .repo-snipit-name,.repo-snipit:hover .repo-snipit-description{color:#4078c0}.leaderboard-action{float:right;margin-top:-3px;margin-left:10px}.leaderboard-list-rank{position:absolute;top:25px;left:0;width:20px;font-weight:300;text-align:right;text-transform:uppercase}.leaderboard-list-item{position:relative;padding-top:20px;padding-bottom:20px;padding-left:35px;border-bottom:1px solid #eee}.leaderboard-list-item:last-child{border-bottom:0}.leaderboard-gravatar{float:left;width:48px;height:48px;border-radius:3px}.leaderboard-list-content{min-height:48px;margin-left:58px}.collection-page .signed-out-comment{margin-left:0}.explore-mail-tease{padding-top:20px;overflow:hidden;background:#202021 url("/images/modules/home/octicons-bg.png") center repeat;border-bottom:1px solid #ddd}.explore-mail-tease h3{color:#fff;text-align:center}.explore-mail-tease-img{display:block;max-width:980px;margin:40px auto -5px;border:1px solid rgba(0,0,0,0.25);border-radius:5px;box-shadow:0 5px 15px rgba(0,0,0,0.15)}.newsletter-frequency{display:-webkit-box;display:flex;-webkit-box-align:stretch;align-items:stretch}.newsletter-frequency-choice{position:relative;-webkit-box-flex:1;flex:1 0 0;margin-right:10px;margin-left:10px;font-weight:normal;cursor:pointer;border:1px solid #eee;border-radius:4px}.newsletter-frequency-choice .notice{position:absolute;right:0;bottom:1em;left:0;z-index:-1;font-weight:bold;color:#6cc644;text-align:center;opacity:0}.newsletter-frequency-choice .notice.visible{bottom:-2em;opacity:1;-webkit-transition:opacity 0.15s ease-in-out;transition:opacity 0.15s ease-in-out}.newsletter-frequency-choice h3{padding:10px;margin:0;font-weight:normal;text-align:center;background-color:#fafafa;border-bottom:1px solid #eee}.newsletter-frequency-choice h3 input{position:relative;top:-2px;margin:0 3px 0 -19px}.newsletter-frequency-choice p{margin:15px}.newsletter-frequency-choice:hover{border-color:#4078c0}.newsletter-frequency-choice:hover h3{color:#fff;background-color:#4078c0;border-color:#4078c0}.newsletter-frequency-choice.selected{border-color:#6cc644;box-shadow:0 0 5px rgba(0,0,0,0.2)}.newsletter-frequency-choice.selected h3{color:#fff;background-color:#6cc644;border-color:#6cc644}.explore-signup-entice{position:relative;padding:15px;font-size:14px;background:#f7f7f7;border:1px solid #ddd;border-radius:3px}.explore-signup-entice h3{margin-bottom:10px;font-size:18px}.explore-signup-entice-inner{position:absolute;top:3px;right:3px;bottom:3px;left:3px;padding-top:30px;text-align:center;background:rgba(247,247,247,0.9)}.explore-signup-entice-wrapper{max-width:500px;padding:5px;margin:0 auto;background:rgba(247,247,247,0.6)}.explore-signup-cta{margin-right:-10px;font-size:13px;vertical-align:middle}.explore-signup-cta a{font-weight:bold}.explore-signup-cta .btn{position:relative;top:-1px}.explore-marketing-header{margin:10px auto 30px;text-align:center}.explore-marketing-header h2{margin:0 0 5px;font-size:32px;font-weight:normal}.explore-marketing-header .lead{margin:5px 0 0}.fakelogin{position:fixed;top:0;z-index:1000;width:100%;font-size:14px;line-height:34px;color:#fff;text-align:center;text-shadow:0 -1px 0 rgba(153,0,0,0.25);background-image:-webkit-linear-gradient(#dc5f59, #b33630);background-image:linear-gradient(#dc5f59, #b33630);border-bottom:1px solid #900}.fakelogin+.header{margin-top:35px}.fakelogin+.server-stats{margin-top:35px}.fakelogin .cancel-impersonation{color:#fff;text-decoration:underline}.file{position:relative;margin-top:20px;margin-bottom:15px;border:1px solid #ddd;border-radius:3px}.file .data.empty{padding:5px 10px;color:#767676}.file .data.suppressed,.file.open .image{display:none}.file.open .data.suppressed{display:block}.file .image{position:relative;padding:30px;text-align:center;background-color:#ddd}.file .image table{margin:0 auto}.file .image td{padding:0 5px;color:#888;text-align:center;vertical-align:top}.file .image td img{max-width:100%}.file .image .border-wrap{position:relative;display:inline-block;line-height:0;background-color:#fff;border:1px solid #767676}.file .image a{display:inline-block;line-height:0}.file .image img,.file .image canvas{max-width:600px;background:url("/images/modules/commit/trans_bg.gif") right bottom #eee;border:1px solid #fff}.file .image .view img,.file .image .view canvas{position:relative;top:0;right:0;max-width:inherit;background:url("/images/modules/commit/trans_bg.gif") right bottom #eee}.file .image .view>span{vertical-align:middle}.file .empty{background:none}.file-header{padding:5px 10px;background-color:#f7f7f7;border-bottom:1px solid #d8d8d8;border-top-left-radius:2px;border-top-right-radius:2px}.file-header::before{display:table;content:""}.file-header::after{display:table;clear:both;content:""}.file-actions{float:right;padding-top:3px}.file-actions select{margin-left:5px}.file-action{display:inline-block;padding-top:4px}.file-info{float:left;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px;line-height:32px}.file-info .octicon{vertical-align:text-bottom}.file-info-divider{display:inline-block;width:1px;height:18px;margin-right:3px;margin-left:3px;vertical-align:middle;background-color:#ddd}.file-mode{text-transform:capitalize}.show-file-notes{display:none}.has-inline-notes .show-file-notes{display:inline-block;margin-right:10px;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}.file-blankslate{border:0;border-radius:0 0 2px 2px}.axis{font-size:10px;fill:#aaa}.axis line{stroke:#eee;shape-rendering:crispedges}.axis path{display:none}.axis .zero line{stroke:#4078c0;stroke-dasharray:3 3;stroke-width:1.5}.graphs .is-graph-loading{min-height:500px}.graphs.wheader h2{padding:1px}.graphs .area{fill:#1db34f;fill-opacity:0.5}.graphs .path{fill:none;stroke:#1db34f;stroke-opacity:1;stroke-width:2px}.graphs .dot{fill:#1db34f;stroke:#16873c;stroke-width:2px}.graphs .dot.padded{stroke:#fff;stroke-width:1px}.graphs .dot.padded circle:hover{fill:#4078c0}.graphs .d3-tip{fill:#333}.graphs .d3-tip text{font-size:11px;fill:#fff}.graphs .dir{float:right;padding-top:5px;font-size:12px;font-weight:normal;line-height:100%;color:#555}.graphs .selection rect{fill:#333;fill-opacity:0.1;stroke:#333;stroke-dasharray:3 3;stroke-opacity:0.4;stroke-width:1px;shape-rendering:crispedges}.graph-filter h3{display:inline-block;font-size:24px;font-weight:300}.graph-filter .info{margin-bottom:20px;color:#767676}h2.ghead::after{display:block;height:0;clear:both;visibility:hidden;content:"."}.graph-canvas .activity{width:400px;padding:10px;margin:100px auto 0;color:#444;text-align:center;border-radius:3px}.graph-canvas .error{padding:10px;color:#900;background:#feeaea;border-radius:3px}.graph-canvas .dots{margin:0 auto}.graph-canvas>.activity{display:none}.graph-loading,.graph-error,.graph-no-usable-data,.graph-empty{display:none}.graph-canvas.is-graph-loading>.activity,.graph-canvas.is-graph-without-usable-data>.activity,.graph-canvas.is-graph-empty>.activity{display:block}.is-graph-loading .graph-loading,.is-graph-empty .graph-empty,.is-graph-without-usable-data .graph-no-usable-data,.is-graph-load-error .graph-error{display:block}.svg-tip{position:absolute;z-index:99999;padding:10px;font-size:12px;color:#bbb;text-align:center;background:rgba(0,0,0,0.8);border-radius:3px}.svg-tip strong{color:#ddd}.svg-tip.is-visible{display:block}.svg-tip::after{position:absolute;bottom:-10px;left:50%;width:5px;height:5px;box-sizing:border-box;margin:0 0 0 -5px;content:" ";border:5px solid transparent;border-top-color:rgba(0,0,0,0.8)}.svg-tip.comparison{padding:0;text-align:left;pointer-events:none}.svg-tip.comparison .title{display:block;padding:10px;margin:0;font-weight:bold;line-height:1;pointer-events:none}.svg-tip.comparison ul{margin:0;white-space:nowrap;list-style:none}.svg-tip.comparison li{display:inline-block;padding:10px}.svg-tip.comparison li:first-child{border-top:3px solid #1db34f;border-right:1px solid #333}.svg-tip.comparison li:last-child{border-top:3px solid #1d7fb3}.svg-tip-one-line{white-space:nowrap}.header{padding-top:10px;padding-bottom:10px;background-color:#f5f5f5;border-bottom:1px solid #e5e5e5}.header-logged-out{padding-top:15px;padding-bottom:15px}.read-only-mode-banner{text-align:center;background-color:#f8e45f;border-bottom-color:#f6dc2e}.header-logo-invertocat{float:left;margin-right:10px;margin-left:-2px;color:#333;white-space:nowrap}.header-logo-invertocat .octicon-mark-github{float:left;font-size:28px}.header-logo-invertocat:hover{color:#4078c0;text-decoration:none}.logo-subbrand{float:left;margin-left:6px;font-size:16px;font-weight:bold;line-height:28px}.header-logo-wordmark{position:relative;float:left;height:26px;margin-right:15px;color:#333}.header-logo-wordmark:hover{color:#4078c0}.notification-indicator .mail-status{position:absolute;top:-2px;right:3px;z-index:2;display:none;width:14px;height:14px;color:#fff;text-align:center;background-image:-webkit-linear-gradient(#7aa1d3, #4078c0);background-image:linear-gradient(#7aa1d3, #4078c0);background-clip:padding-box;border:2px solid #f3f3f3;border-radius:50%}.notification-indicator .mail-status.unread{display:inline-block}.notification-indicator:hover .mail-status{background-color:#4078c0}.header-search{float:left;width:360px;margin-right:10px}.header-search-wrapper{display:table;width:100%;max-width:100%;min-height:0;padding:0;font-size:12px;font-weight:normal;vertical-align:middle}.header-search-input{display:table-cell;width:99%;min-height:26px;padding-top:0;padding-bottom:0;background:none;border:0;box-shadow:none}.header-search-input:focus{border:0;box-shadow:none}.header-search-input::-ms-clear{display:none}.header-search-scope{display:none;width:1%;padding-right:8px;padding-left:8px;color:#767676;white-space:nowrap;vertical-align:middle;border-right:1px solid #eee;border-top-left-radius:2px;border-bottom-left-radius:2px}.scoped-search .header-search-scope{display:table-cell}.scoped-search .form-control.focus .header-search-scope{color:#4078c0;background-color:#edf2f9;border-color:#c6d7ec}.header-nav{list-style:none}.header-nav-item{float:left}.header-nav-item.active .dropdown-menu-content{display:block}.header-nav-item.active .tooltipped::before,.header-nav-item.active .tooltipped::after{display:none}.header-nav-item .dropdown-menu{width:180px;margin-top:13px}.header-nav-link{display:block;padding:4px 8px;font-size:13px;font-weight:bold;line-height:20px;color:#333}.header-nav-link:hover,.header-nav-link:focus{color:#4078c0;text-decoration:none}.header-nav-link:hover .dropdown-caret,.header-nav-link:focus .dropdown-caret{border-top-color:#4078c0}.user-nav{margin-right:-8px}.user-nav .header-nav-link{height:28px}.user-nav .octicon{width:16px;margin-top:1px;text-align:center}.user-nav .octicon-plus{margin-right:1px}.user-nav .octicon-bell{vertical-align:text-bottom}.user-nav .avatar{float:left;margin-right:5px}.header-nav-current-user{padding-bottom:0;font-size:inherit}.header-nav-current-user .css-truncate-target{max-width:100%}.header-actions{float:right;margin-top:-3px;margin-bottom:-3px}.header-actions .btn{margin-left:5px}.enterprise .header{background-color:#2a2c2e;border-bottom-color:#121313}.is-stats .enterprise .header{box-shadow:inset 0 1px 0 rgba(255,255,255,0.05)}.enterprise .header-logo-wordmark,.enterprise .header-logo-invertocat,.enterprise .header-nav-link{color:#c8c8ca}.enterprise .header-logo-wordmark:hover,.enterprise .header-logo-wordmark:focus,.enterprise .header-logo-invertocat:hover,.enterprise .header-logo-invertocat:focus,.enterprise .header-nav-link:hover,.enterprise .header-nav-link:focus{color:#fafafa}.enterprise .header-nav-link:hover .dropdown-caret,.enterprise .header-nav-link:focus .dropdown-caret{border-top-color:#fafafa}.enterprise .notification-indicator .mail-status{border-color:#2a2c2e}.enterprise .notification-indicator:hover .mail-status{background-color:#d26911}.enterprise .header-actions .btn{border:0;box-shadow:inset 0 1px 0 rgba(255,255,255,0.25),0 1px 1px rgba(0,0,0,0.5)}.enterprise .header-search-scope{color:#c8c8ca;background-color:#5b5f63;border-color:#424649}.enterprise .header-search-wrapper{color:#fafafa;background-color:#4f5256;border-color:#121313}.enterprise .header-search-wrapper.focus{background-color:#55595d;border-color:#000;box-shadow:inset 0 1px 0 rgba(0,0,0,0.075)}.enterprise .header-search-wrapper.focus .header-search-scope{color:#fff;background-color:#676c71;border-color:#4f5256}.enterprise .header-search-input{color:#fff}.hosted.logged-in .header-search{width:280px}.hosted.logged-in .header-search-input{width:180px}.unsupported-browser{padding:15px 0;color:#211e14;background-image:-webkit-linear-gradient(#feefae, #fae692);background-image:linear-gradient(#feefae, #fae692);border-bottom:1px solid #b3a569}.unsupported-browser .container{background:url("/images/icons/ie-notice.png") no-repeat 0 5px}.unsupported-browser h5{padding-left:48px;margin:5px 0 2px;font-size:13px}.unsupported-browser p{padding-left:48px;margin:0}.unsupported-browser .btn{float:right;margin-top:5px;margin-left:8px}.mobile-banner button.switch-to-mobile{display:block;width:100%;padding:30px 0 45px;font-size:60px;font-weight:bold;color:#eaeaea;text-align:center;background-color:#444;border:0}.accessibility-aid{position:absolute;width:1px;height:1px;margin:0;overflow:hidden;clip:rect(1px, 1px, 1px, 1px)}.accessibility-aid:focus{top:0;z-index:1;width:auto;height:auto;padding:0 10px;clip:auto;font-weight:bold;line-height:49px;color:#333;text-decoration:none;background:#f5f5f5}.is-stats .accessibility-aid:focus{top:34px}.job-postings-subnav-search{width:320px;margin-left:-1px}.job-posting-list{position:relative}.job-posting-expiration-warning,.job-posting-payment-warning{margin-bottom:20px}.job-posting-list-item{position:relative;display:table;width:100%;padding:20px 10px;list-style:none;border-top:1px solid #eee}.job-posting-list-item.unpublished{font-style:italic}.job-posting-list-item .job-posting-unpublished{font-size:13px;font-weight:normal;color:#999;text-transform:uppercase}.job-posting-list-item .job-posting-unpublished::before{content:"\b7\a0"}.job-posting-list-item.expired{background-color:#f1f1f1}.job-posting-list-item.expired+.expired{border-top:1px solid #fff}.job-posting-list-meta,.job-posting-main-content,.job-posting-logo{display:table-cell;vertical-align:top}.job-posting-list-name{margin:0;font-size:18px;line-height:1.2}.job-posting-list-kind{max-width:550px;margin-top:4px;margin-bottom:0;font-size:14px;color:#666}.job-posting-list-meta{width:20%;padding-left:20px;font-size:13px;color:#888}.job-posting-list-info-location,.job-posting-list-info-remote{margin-top:0;margin-bottom:4px;color:#888;text-align:right}.job-posting-list-info-location{display:block;max-width:18em;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.job-posting-list-info-stats{float:right;padding-left:20px;margin-top:0;margin-bottom:4px;color:#888;text-align:right}.job-posting-description{margin-top:30px;font-size:14px}.visitors-graph+.columns .job-posting-description{margin-top:0}.job-posting-sidebar-title{padding:15px 15px 0;margin:-15px -15px 0;font-size:20px}.job-posting-details-list .job-posting-detail{display:inline;list-style:none}.job-posting-details-list .job-posting-detail+.job-posting-detail::before{content:"\a0\b7\a0"}.job-posting-apply-instructions .user-markdown{margin-top:8px;font-size:14px;color:#767676}.job-posting-apply-instructions .md-list-item{margin-left:20px}.job-posting-apply-instructions .md-list-item+.md-list-item{margin-top:5px}.job-posting-apply-instructions .md-list{margin-bottom:10px}.job-posting{margin-bottom:30px}.job-posting .job-posting-publish-form{display:inline-block;margin-left:8px}.job-posting .flash{margin-bottom:20px}.job-posting .gh-header-meta{padding-bottom:0;margin-top:5px;border-bottom:0}.job-posting .visitors-graph{margin-top:20px}.job-posting-form-edit-section{width:700px}.new-job-posting-form,.new-job-posting-credit-form{color:#4a4a4a}.new-job-posting-form .form-section,.new-job-posting-credit-form .form-section{padding-bottom:20px}.new-job-posting-form label,.new-job-posting-credit-form label{display:block;padding-bottom:5px}.job-credit-description{margin-bottom:20px;font-size:14px}.new-job-posting-form{width:700px;margin:0 auto}.new-job-posting-form .subhead-description+.subhead-description{margin-top:5px}.new-job-posting-form .suggester-container{left:21px}.new-job-posting-form .job-posting-location{width:20em;margin-left:1px}.new-job-posting-form .job-posting-title,.new-job-posting-form .job-posting-apply-link,.new-job-posting-form select,.new-job-posting-form textarea,.new-job-posting-form .job-posting-renew-count,.new-job-posting-form .job-posting-identifier,.new-job-posting-form .job-posting-company-url,.new-job-posting-form .job-posting-company-logo-field,.new-job-posting-form .job-posting-company,.new-job-posting-form .job-posting-organization{width:100%}.new-job-posting-form .write-content{margin:0}.new-job-posting-form label{font-size:16px}.new-job-posting-form label.job-posting-billing-target-label{display:inline;padding-bottom:0;font-size:13px}.new-job-posting-form .small{font-size:12px}.new-job-posting-form .toolbar-help{margin-bottom:0}.new-job-posting-form .toolbar-help .tabnav-extra{padding-top:0;margin-left:0}.new-job-posting-credit-form{font-size:14px}.new-job-posting-credit-form .job-posting-credit-price{font-size:21px;font-weight:700}.new-job-posting-credit-form .job-posting-credit-total-label{padding-right:3em}.new-job-posting-credit-form .job-posting-credit-credits-purchased{width:5em;font-size:14px;text-align:right}.new-job-posting-credit-form .job-posting-credit-card{text-align:left}.purchase-job-credits{width:700px}.purchase-job-credits .gh-header-meta{margin-bottom:20px}.job-posting-summary .job-posting-price{font-size:33px;font-weight:lighter}.job-posting-summary .job-posting-price .amount{font-weight:normal;color:#6cc644}.job-posting-credit-card{margin-top:16px;text-align:right;vertical-align:middle}.search-job-postings-header{width:940px;margin:40px auto 0;color:#4a4a4a}.search-job-postings-header h1{margin-top:50px;font-size:32px;font-weight:normal;text-align:center}.search-job-postings-header h3{font-size:20px;font-weight:lighter;text-align:center}.job-posting-remote-label{margin-bottom:10px}.applied-suggestions{margin:0 0 10px;font-size:14px;list-style:none}.applied-suggestions .applied-suggestion+.applied-suggestion{margin-top:10px}.applied-suggestions .remove-suggestion{font-weight:700;color:#4078c0;background-color:transparent;border:0}.job-search-suggester-container{position:relative}.job-search-suggester-container .job-search-location-suggestions{display:inline-block}.job-search-suggester-container .octospinner{position:absolute;top:9px;right:9px}.job-search-suggester-container .suggester-container{top:100%;width:100%}.job-search-suggester-container .suggester-container .suggester{margin-top:-1px}.job-search-suggester-container .suggestion{cursor:pointer}.search-job-postings{margin-top:20px;margin-bottom:40px}.search-job-postings.results{margin-top:0;margin-bottom:0}.search-job-postings .column.two-thirds{padding:0}.search-job-postings .column.two-sevenths{width:28.5%}.search-job-postings .column.one-seventh{width:14.2%}.search-job-postings label{display:block;padding-bottom:5px;font-size:18px;color:#4a4a4a}.search-job-postings input,.search-job-postings select{width:100%;color:#767676}.search-job-postings [type="submit"]{width:100%;margin-top:30px;color:#4a4a4a}.search-job-postings .form-checkbox{margin-top:5px}.search-job-postings .form-checkbox input{width:20px}.search-job-postings .form-checkbox label{display:inline;font-size:13px;font-weight:normal;color:#767676}.search-job-postings-sidebar{float:left;width:230px}.search-job-postings-sidebar .post-job-link{margin-bottom:20px;text-align:center}.search-job-postings-sidebar .featured{width:100%;margin-bottom:20px;font-size:15px;font-weight:normal}.search-job-postings-sidebar .featured.btn{color:#fff;background:#4078c0;border:1px solid #4078c0}.search-job-postings-sidebar .job-search-watch-form{margin-bottom:20px}.search-job-postings-sidebar .job-search-watch-form .watch{width:100%}.job-search-watch-form .job-search-title{width:100%;margin-top:5px}.job-search-watch-fields{padding:16px}.search-job-postings-watched{color:#4a4a4a}.search-job-postings-watched .menu-item{padding:8px 10px 3px}.search-job-postings-watched .menu-item .octicon{margin-right:0}.search-job-postings-watched .more-mega{font-size:50px;opacity:0.4}.search-job-postings-watched .saved-job-posting-search-link{display:inline-block;max-width:88%;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.search-job-postings-watched h3{margin:0 0 5px;font-size:15px;font-weight:normal;color:#4a4a4a}.search-job-postings-watched .job-search-unwatch-form{float:right}.search-job-postings-watched .job-search-unwatch-form .job-search-unwatch{padding:0;background-color:transparent;border:0}.job-posting-list .results-heading,.search-job-postings-main .results-heading{padding-top:0;margin:0 0 10px;font-size:24px;font-weight:300}.search-job-postings-main{float:right;width:730px}.search-job-postings-main .sort-bar{padding-bottom:0;margin-bottom:0;border-bottom:0}.job-posting-logo{display:table-cell;width:55px;vertical-align:middle}.job-posting-logo .job-posting-org-link{display:inline-block}.job-posting-logo .avatar{width:40px}.org-job-postings-results .pagehead{padding-bottom:0}.job-search-reset-query-wrapper{margin-bottom:10px}.job-search-reset-query{font-weight:bold;color:#767676}.job-search-reset-query .job-search-reset-query-block{display:inline-block;width:20px;height:20px;margin-right:3px;line-height:20px;color:#fff;text-align:center;background-color:#767676;border-radius:3px}.job-search-reset-query .job-search-reset-query-block .octicon-x{line-height:20px}.job-search-reset-query:hover{color:#4078c0;text-decoration:none}.job-search-reset-query:hover .job-search-reset-query-block{background-color:#4078c0}.blankslate+.job-posting-credit-header{margin-top:30px}.job-posting-credit-header{margin-bottom:10px;font-size:16px}.job-posting-org-payment{margin-bottom:10px}.job-posting-billing-target-choice{margin-right:5px}.job-postings-return-footer p{margin-bottom:10px}.job-postings-return-link{display:block;margin-top:45px}.job-posting-company-info .job-posting-company-logo{max-width:100%}.job-posting-company-info .job-posting-company-logo-link{display:block;margin-top:8px}.job-posting-company-info .job-posting-company-link{display:block;margin-top:8px}.pagehead .job-posting-company-link{color:#333}.job-posting-error-explanation{margin-bottom:20px}.job-posting-error-explanation .job-posting-error-list-item{list-style:none}.job-saved-search-info{display:inline-block;color:#797979}.job-saved-search-info .octicon{vertical-align:middle}.job-posting-credits-list .job-posting-credit-list-item{list-style:none}.job-posting-credits-list .job-posting-credit-list-item+.job-posting-credit-list-item{padding-top:10px;margin-top:10px;border-top:1px solid #eee}.job-posting-credits-list .job-posting-credit-metadata{margin-top:5px;font-size:12px;color:#797979}.job-posting-credits-list .job-posting-credit-user+.job-posting-credit-date::before{padding-right:0.3em;padding-left:0.3em;content:"\b7"}.hiring-managers-list>li:last-child{padding-bottom:0}.hiring-manager-pending-invitation{margin-top:20px}.hooks-listing .boxed-group-action.select-menu{z-index:auto}.hooks-listing .boxed-group-inner{padding:0 10px;margin-bottom:10px}.hook-item a:hover{text-decoration:none}.hook-item .item-status{float:left;width:16px;margin-right:8px;text-align:center}.hook-item .description{color:#999}.hook-item .description .css-truncate-target{max-width:160px}.hook-item .icon-for-success,.hook-item .icon-for-failure,.hook-item .icon-for-pending,.hook-item .icon-for-inactive{display:none}.hook-item.success .icon-for-success{display:inline-block;color:#6cc644}.hook-item.failure .icon-for-failure{display:inline-block;color:#bd2c00}.hook-item.pending .icon-for-pending{display:inline-block;color:#999}.hook-item.inactive .icon-for-inactive{display:inline-block;color:#999}.hook-item .icon-for-enabled,.hook-item .icon-for-disabled{display:none}.hook-item.enabled .icon-for-enabled{display:inline-block;color:#6cc644}.hook-item.disabled .icon-for-disabled{display:inline-block;color:#ccc}.hook-item .hook-error-message{margin-left:24px;color:#bd2c00}.hook-url.css-truncate-target{max-width:360px}.hook-events-field .hook-event-selector{display:none}.hook-events-field.is-custom .hook-event-selector{display:block}.hook-event-selector{margin-left:10px}.hook-event{display:inline-block;width:310px;padding:5px 0 5px 30px;margin:0}.hook-event label{font-weight:normal}.hook-event.hook-event-condensed{width:240px;padding-right:20px;padding-bottom:10px;padding-left:20px}.hook-event.hook-event-condensed .text-muted{font-size:12px;font-weight:normal}.hook-event .text-small{margin:0}.hook-event-choice{font-weight:normal}.hook-form.is-ssl .ssl-hook-fields{display:block}.hook-form .ssl-hook-fields{display:none}.hook-form .ssl-hook-fields .disable-ssl-verification-modal,.hook-form .ssl-hook-fields .enable-ssl-verification{display:none}.hook-form .ssl-hook-fields.is-not-verifying-ssl .enable-ssl-verification{display:block}.hook-form .ssl-hook-fields.is-not-verifying-ssl .disable-ssl-verification{display:none}.hook-form .disable-ssl-verification .actions{margin-top:-4px}.hook-form .invalid-url-notice{display:none;padding:7px 4px}.hook-form .invalid-url-notice .octicon-alert{position:relative;top:1px}.hook-form.is-invalid-url .invalid-url-notice{display:block}.hooks-oap-warning{margin-top:0}.hooks-oap-warning ul{margin:10px 0}.hooks-oap-warning ul li{margin-left:16px}.hook-secret .hook-secret-standin{display:block}.hook-secret .hook-secret-field{display:none}.hook-secret.open .hook-secret-standin{display:none}.hook-secret.open .hook-secret-field{display:block}.hook-deliveries-list .loading-message{display:block}.hook-deliveries-list .error-message{display:none}.hook-deliveries-list.is-error .loading-message{display:none}.hook-deliveries-list.is-error .error-message{display:block}.hook-deliveries-list .spinner{display:inline-block;margin:0;vertical-align:top}.hook-deliveries-list .hook-delivery-item:hover{background-color:transparent}.hook-deliveries-list .item-status{display:inline-block;width:16px;margin-right:5px;text-align:center}.hook-deliveries-list .item-status .icon-for-success,.hook-deliveries-list .item-status .icon-for-failure,.hook-deliveries-list .item-status .icon-for-pending{display:none}.hook-deliveries-list .item-status.success{color:#6cc644;visibility:visible}.hook-deliveries-list .item-status.success .icon-for-success{display:inline-block}.hook-deliveries-list .item-status.failure{color:#bd2c00}.hook-deliveries-list .item-status.failure .icon-for-failure{display:inline-block}.hook-deliveries-list .item-status.pending{color:#999}.hook-deliveries-list .item-status.pending .icon-for-pending{display:inline-block}.hook-deliveries-pagination-loading-message{display:none}.hook-deliveries-pagination-loading-message .animated-ellipsis-container{text-align:left}.hook-deliveries-pagination.loading .hook-deliveries-pagination-button{display:none}.hook-deliveries-pagination.loading .hook-deliveries-pagination-loading-message{display:block}.boxed-group-list li.hook-delivery-item{padding:10px}.hook-delivery-item .hook-delivery-details{display:none}.hook-delivery-item .hook-delivery-details .loading-message,.hook-delivery-item .hook-delivery-details .error-message{display:none}.hook-delivery-item .hook-delivery-details.is-loading .loading-message{display:block}.hook-delivery-item .hook-delivery-details.has-error .error-message{display:block}.hook-delivery-item.open .hook-delivery-details{display:block}.hook-delivery-item .loading-message{text-align:center}.hook-delivery-time{float:right;margin-right:10px;font-size:10px;color:#999}.hook-delivery-guid{display:inline-block;padding:2px 6px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px;color:rgba(0,0,0,0.5);background-color:rgba(209,227,237,0.5);border-radius:3px}.hook-delivery-guid .octicon{margin:1px -2px 0 0;color:#b0c4ce}.hook-delivery-actions{padding-top:1px}.boxed-group-list>li.hook-delivery-item .btn-sm{margin:0}.boxed-group-list>li.hook-delivery-item .hook-delivery-details .btn-sm{margin:5px 0 0}.hook-deliveries-list .error-message,.hook-delivery-details .error-message{padding:7px 4px;margin:10px 0}.hook-deliveries-list .error-message .octicon,.hook-delivery-details .error-message .octicon{position:relative;top:1px}.boxed-group span.animated-ellipsis-container,.boxed-group span.animated-ellipsis{padding:0}.boxed-group .animated-ellipsis-container{line-height:1.3}.hook-delivery-details{clear:right}.hook-delivery-details .error-message{margin-bottom:0}.hook-delivery-details .tabnav-tabcontent{display:none}.hook-delivery-details .tabnav-tabcontent.selected{display:block}.hook-delivery-details hr{margin:10px 0}.hook-delivery-details pre{padding:7px 12px;margin:10px 0;overflow:auto;font-size:13px;line-height:1.5;background-color:#f8f8f8;border:1px solid #ddd;border-radius:3px}.hook-delivery-details .tabnav{margin:10px 0}.hook-delivery-details h4.remote-call-header{margin:20px 0 10px;border-bottom:1px solid #999}.hook-delivery-response-status{display:inline-block;padding:4px 6px 3px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:10px;line-height:1.1;color:#fff;background-color:#bd2c00;border-radius:3px}.hook-delivery-response-status[data-response-status^="2"]{background-color:#6cc644}.redelivery-dialog .pending-message{display:block}.redelivery-dialog .failure-message{display:none}.redelivery-dialog.failed{color:#9c2400;background-image:-webkit-linear-gradient(#f8d8d8, #efd0d0);background-image:linear-gradient(#f8d8d8, #efd0d0);border-color:#da9797}.redelivery-dialog.failed .pending-message{display:none}.redelivery-dialog.failed .failure-message{display:block}.redelivering-hook-delivery .error-message{display:none}.redelivering-hook-delivery.is-error .loading-message{display:none}.redelivering-hook-delivery.is-error .error-message{display:block}.test-hook-message .success-message,.test-hook-message .error-message{display:none;margin-top:10px}.test-hook-message.success .success-message{display:block}.test-hook-message.error .error-message{display:block}.item-name{float:left;font-weight:bold}.installation-permissions .octicon-check{color:#6cc644}.installation-permissions li:hover{background:none}.integration-meta-head{font-size:16px;color:#767676}.integration-info-section{margin-bottom:40px}.integration-info-section h3{margin:0 0 10px;color:#767676}.integration-info-section ul{list-style:none}.integration-info-section span{margin-right:10px}.confirm-integration-name.css-truncate-target{max-width:300px}.integrations-select-repos{max-height:138px;overflow-y:scroll;border-radius:3px}.integrations-select-repos .mini-repo-list-item{padding:8px 64px 8px 30px}.integrations-select-repos .mini-repo-list-item:hover .repo,.integrations-select-repos .mini-repo-list-item:hover .owner{text-decoration:none}.integrations-select-repos .mini-repo-list-item .css-truncate-target{max-width:345px}.integrations-select-repos::-webkit-scrollbar{width:10px}.integrations-select-repos::-webkit-scrollbar-thumb{background-color:rgba(0,0,0,0.5);border:solid #fff 2px;border-radius:6px;box-shadow:0 0 1px rgba(255,255,255,0.5)}.integrations-select-repos::-webkit-scrollbar-track-piece{background:transparent}.integrations-repository-picker{position:relative;top:-5px;width:440px}.integrations-repository-picker .autocomplete-results{border:0}.integrations-repository-picker .subnav-search{margin-bottom:5px;margin-left:0}.integrations-repository-picker .flash{padding:10px;margin-bottom:10px}dl.form-group>dd .integrations-repository-picker .form-control{margin-right:0}.integrations-repository-picker .octicon-x{position:absolute;top:8px;right:10px;cursor:pointer}.list-group-label{display:inline-block;float:left;max-width:70%;overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.target-avatar{position:relative;top:-2px}.issues-reset-query-wrapper{margin-bottom:20px}.issues-reset-query{font-weight:bold;color:#767676}.issues-reset-query:hover{color:#4078c0;text-decoration:none}.issues-reset-query:hover .issues-reset-query-icon{background-color:#4078c0}.issues-reset-query-icon{width:18px;height:18px;padding:1px;margin-right:3px;color:#fff;text-align:center;background-color:#767676;border-radius:3px}.table-list-milestones .table-list-cell{padding:15px 20px}.table-list-milestones .stat{display:inline-block;font-size:14px;font-weight:bold;line-height:1.2;color:#555;white-space:nowrap}.table-list-milestones .stat a{color:inherit}.table-list-milestones .stat+.stat{margin-left:15px}.table-list-milestones .stat-label{font-weight:normal;color:#767676}.milestone-title{width:500px}.milestone-title-link{margin-top:0;margin-bottom:5px;font-size:24px;font-weight:normal;line-height:1.2}.milestone-title-link a{color:#333}.milestone-title-link a:hover{color:#4078c0}.milestone-progress{width:420px}.milestone-progress .progress-bar{margin-top:7px;margin-bottom:12px}.milestone-meta{font-size:14px}.milestone-meta-item{display:inline-block;margin-right:10px}.milestone-meta-item .octicon{width:16px;text-align:center}.milestone-description-html{display:none}.milestone-description{margin-top:5px}.milestone-description .expand-more{color:#4078c0;cursor:pointer}.milestone-description .expand-more:hover{text-decoration:underline}.milestone-description.open .milestone-description-plaintext{display:none}.milestone-description.open .milestone-description-html{display:block}.milestone-actions{margin-top:8px;font-size:13px}.milestone-action{display:inline-block;margin-right:10px}.milestone-calender-container{margin-left:30px}.issue-reorder-warning{z-index:110}.task-progress{color:#767676;text-decoration:none}.task-progress .octicon{margin-right:5px;color:#999;vertical-align:bottom}.task-progress .progress-bar{display:inline-block;width:120px;height:5px;vertical-align:2px;background-color:#eee}.task-progress .progress-bar .progress{background-color:#ccc}.task-progress-counts{display:inline-block;margin-right:6px;margin-left:-2px;font-size:12px}a.task-progress:hover{color:#4078c0}a.task-progress:hover .octicon{color:inherit}a.task-progress:hover .progress-bar .progress{background-color:#4078c0}.issues-listing{position:relative}.issues-listing .octocat-search{position:absolute;right:0;height:250px;margin:-132px -4px;-webkit-transform:scaleX(-1);transform:scaleX(-1)}.issues-listing .table-list-issues .issue-title{width:740px;padding-top:12px}.issues-listing .table-list-issues .labels{display:inline-block;margin-bottom:2px;vertical-align:1px}.issues-listing .table-list-issues .navigation-focus{background-color:#f5f5f5}.issue-title-link{padding-right:3px;margin-bottom:2px;font-size:15px;font-weight:bold;line-height:1.2;color:#333}.issue-title-link:hover{color:#4078c0;text-decoration:none}.issue-title-link:hover .num{color:inherit}.issue-pr-status{display:inline-block;margin-right:3px;vertical-align:top}.issue-meta{margin-top:1px;margin-bottom:2px;font-weight:normal;color:#767676}.issue-meta-section{margin-right:10px}.issue-meta-section .octicon{color:#ccc;vertical-align:bottom}.issue-milestone{max-width:240px}.milestone-link .octicon{font-size:14px}.milestone-link:hover .octicon{color:inherit}.new-issue-form{padding-top:20px;margin-top:15px;border-top:1px solid #ddd}.new-issue-form::before{display:table;content:""}.new-issue-form::after{display:table;clear:both;content:""}.new-issue-form .discussion-timeline::before{display:none}.new-pr-form{margin-top:15px;margin-bottom:20px}.new-pr-form::before{display:table;content:""}.new-pr-form::after{display:table;clear:both;content:""}.new-pr-form .discussion-timeline::before{display:none}.new-pr-form .discussion-sidebar{position:static}.label-select-menu .color{display:inline-block;width:14px;height:14px;margin-right:2px;margin-bottom:2px;vertical-align:middle;border-radius:3px}.label-select-menu .selected .select-menu-item-icon{color:inherit !important}.label-select-menu .selected:active{background-color:transparent !important}.label-select-menu .select-menu-item{position:relative}.label-select-menu .select-menu-item.navigation-focus{color:inherit;background-color:#f4f4f4}.label-select-menu .select-menu-item.navigation-focus .select-menu-item-icon{color:transparent}.label-select-menu>form{position:relative}.closed-banner{height:7px;margin:15px 0 15px 60px;overflow:hidden;background:url("/images/modules/comments/closed_pattern.gif");border-radius:3px}.subnav .btn+.issues-search{padding-right:10px;border-right:1px solid #eee}.reaction-sort-item{float:left;width:39px;padding:5px;margin-top:5px;text-align:center;pointer-events:all;border:solid 1px transparent;border-radius:3px;opacity:0.7}.reaction-sort-item:hover,.reaction-sort-item.selected:hover,.reaction-sort-item.navigation-focus{text-decoration:none;background-color:#4078c0;opacity:1}.reaction-sort-item.selected{background-color:#f2f8fa;border-color:#4078c0;opacity:1}html.emoji-size-boost .reaction-sort-item g-emoji{margin-left:-3px}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){html.emoji-size-boost .reaction-sort-item g-emoji{margin-left:0}}.privacy-selection{margin-left:10px;clear:left}.privacy-selection label{font-weight:normal}.merge-branch-heading{margin:0;line-height:1;color:#333}.merge-branch-description{margin-right:160px;margin-bottom:-5px;line-height:1.6em;color:#767676}.merge-branch-description .zeroclipboard-link .octicon{top:2px}.alt-merge-options{display:inline-block;margin-bottom:0;margin-left:4px;vertical-align:middle}.merged .merge-branch-description .commit-ref .css-truncate-target{max-width:180px}.merge-branch-prh-output{margin-top:10px}.merge-branch-form{display:none;padding-left:64px}.merge-branch.open .merge-branch-form{display:block}.merge-branch.open .merge-message{display:none}.merge-branch-manually{display:none;padding-top:15px;margin-top:14px;background-color:transparent;border-top:1px solid #ddd}.merge-branch-manually p{margin-bottom:0}.merge-branch-manually h3{margin-bottom:10px}.merge-branch-manually .intro{padding-bottom:10px;margin-top:0}.merge-branch-manually .step{margin:15px 0 5px}.merge-branch-manually .url-box{padding:0;margin-left:0;border:0}.merge-branch-manually .clone-urls{width:100%}.merge-branch-manually .copyable-terminal{background-color:#f2f2f2}.open .merge-branch-manually{display:block}.merge-methods .btn-group-merge{display:inline-block}.merge-methods .btn-group-squash{display:none;margin-left:0}.merge-methods.is-squashing .btn-group-merge{display:none}.merge-methods.is-squashing .btn-group-squash{display:inline-block}.unavailable-merge-method{display:block;margin-top:6px;color:#c9510c}.navigation-focus.disabled .unavailable-merge-method{color:#fff}.network .network-tree{vertical-align:middle}.network .gravatar{margin-right:4px;vertical-align:middle;border-radius:3px}.network .octicon{display:inline-block;width:16px;margin-left:2px;text-align:center;vertical-align:middle}.network .current-repository{background-color:#fff6a9}.network .network-graph-container{position:relative;margin-bottom:20px;overflow:hidden;line-height:0;border:1px solid #ddd;border-radius:3px}.network .network-graph-container .large-loading-area{position:absolute;top:0;right:0;left:0}.page-new-repo .octicon-repo{color:#bbb}.page-new-repo .octicon-lock{color:#e9dba5}.page-new-repo ul.repo-templates{margin:10px 0}.page-new-repo ul.repo-templates>li{display:inline-block;margin:0 10px 0 0;list-style-type:none}.page-new-repo ul.repo-templates .select-menu{float:left}.page-new-repo .form-checkbox .octicon{float:left;margin-right:5px}.page-new-repo .license-info{float:left;margin-top:5px;margin-left:10px;color:#ccc}.new-repo-container{width:700px;margin:40px auto 0}.new-repo-container .subhead{margin-bottom:30px}.owner-reponame dl.form-group{margin-top:5px;margin-bottom:0}.owner-reponame .slash{float:left;padding-top:32px;margin:0 8px;font-size:21px;color:#666}.owner-reponame .icon-preview{position:absolute;top:23px;left:-115px;display:none;width:100px;font-size:32px;text-align:right}.owner-reponame .icon-preview.icon-preview-public{top:25px}.reponame-suggestion{color:#34631a;cursor:pointer}.upgrade-upsell{padding-left:33px}.cc-upgrade{padding-left:20px}.featured-license{font-weight:bold}.license-container{padding-left:15px;border-left:1px solid #ccc}.notification-routing .notification-email .edit-link{margin-right:10px;font-weight:bold}.notification-routing .notification-email .btn-sm{float:none;margin:-2px 0 0}.notification-routing .notification-email .edit-form{display:none}.notification-routing .notification-email.open .edit-form{display:block}.notification-routing .notification-email.open .email-display{display:none}.notifications .list-group-item{padding-top:8px;padding-bottom:8px;padding-left:35px;border-width:1px 0 0}.notifications .list-group-item:first-child{border:0}.notifications .list-group-item-name{display:block;max-width:400px;font-size:14px;line-height:1.5em}.notifications .list-group-item-name a{display:block;max-width:460px}.notifications .notifications-more{padding:0}.notifications .notifications-more>a{display:block;padding:10px 15px;font-weight:bold;color:#4078c0;text-align:center}.notifications .notifications-more>a:hover{text-decoration:underline}.notifications .read .type-icon{color:#767676}.notifications .read .list-group-item-name>a{color:#767676}.notifications .read .notification-actions{color:#767676}.notifications .read .avatar-stack{opacity:0.5}.notifications .read .undo{display:block}.notifications .read .delete{visibility:hidden}.notifications .read.navigation-focus{background-color:#f5f9fc}.notifications .muted .unmute{display:block}.notifications .muted .mute{display:none}.notifications .unmute{display:none}.type-icon-state-none{color:#767676}.type-icon-state-open{color:#6cc644}.type-icon-state-closed{color:#bd2c00}.type-icon-state-merged{color:#6e5494}.notifications-list{float:left;width:100%}.notifications-list .notifications-repo-link{max-width:500px}.notifications-list .boxed-group .text-success{position:absolute;right:3px;width:210px;margin-top:4px;color:#6cc644;text-align:right;visibility:hidden;opacity:0;-webkit-transition:opacity 0.35s ease-in-out, -webkit-transform 0.35s ease-in-out;transition:opacity 0.35s ease-in-out, transform 0.35s ease-in-out;-webkit-transform:translateX(10px);transform:translateX(10px)}.notifications-list .mark-all-as-read{padding:2px 6px 5px 10px;margin-top:0;margin-right:0;line-height:20px;color:#767676;background-color:transparent;border:0}.notifications-list .mark-all-as-read-confirmed .text-success{visibility:visible;opacity:1;-webkit-transform:translateX(0);transform:translateX(0)}.notifications-list .mark-all-as-read-confirmed .mark-all-as-read{visibility:hidden}.notifications-list .confirmation{max-height:0;padding:0;overflow:hidden;color:#666;text-align:center;opacity:0;-webkit-transition:opacity 0.4s ease-in-out, max-height 0.4s ease-in-out, padding 0.4s ease-in-out;transition:opacity 0.4s ease-in-out, max-height 0.4s ease-in-out, padding 0.4s ease-in-out}.notifications-list .confirmation+.list-group-item{margin-top:-1px;border-top-color:#d5d5d5}.notifications-list .confirmation.mark-all-as-read-confirmed{max-height:300px;padding:10px 0;opacity:1}.notification-actions{position:absolute;top:8px;right:10px;list-style:none}.notification-actions li{float:right;margin-left:10px;font-size:16px;line-height:20px}.notification-actions .age{width:120px;font-size:12px;color:#767676}.notification-actions .undo{position:absolute;top:0;display:none}.notification-actions .btn-link{padding-right:5px;padding-left:5px;line-height:inherit;color:#767676}.notification-actions .btn-link:hover{color:#4078c0;text-decoration:none}.repo-subscription-container{width:600px;margin-right:auto;margin-left:auto}.repo-subscription-container .spinner{float:right}.repo-subscription-container h2{margin-top:24px;margin-bottom:12px;font-size:22px;font-weight:normal}.repo-subscription-container .intro{font-size:14px;color:#666}.repo-subscription-label{display:inline-block}.subscriptions-content .sorted-by{float:right;margin:10px;font-size:12px;color:#999}.subscriptions-content .repo-icon{margin-right:5px;color:#666;vertical-align:middle}.subscriptions-content .repo-list form{display:inline}.subscriptions-content .repo-list .only-loading{display:none}.subscriptions-content .repo-list .loading .only-loading{display:inline-block}.subscriptions-content .repo-list .only-unsubed{display:none}.subscriptions-content .repo-list .unsubscribed .only-unsubed{display:inline}.subscriptions-content .repo-list .unsubscribed .only-subed{display:none}.subscriptions-content .repo-list .only-unignored{display:none}.subscriptions-content .repo-list .unsubscribed .only-unignored{display:inline}.subscriptions-content .repo-list .unsubscribed .only-ignored{display:none}.thread-subscription-status{padding:10px;margin:40px 0 20px;color:#767676;background-color:#fff;border:1px solid #eee;border-radius:3px}.thread-subscription-status .octicon-radio-tower{margin-right:10px;margin-left:4px;color:#ccc;vertical-align:middle}.thread-subscription-status .btn-sm>.octicon{margin-right:1px}.thread-subscription-status .reason{display:inline-block;margin:0 10px;vertical-align:middle}.thread-subscription-status .thread-subscribe-form{display:inline-block;vertical-align:middle}.subscription .loading{opacity:0.5}.oauth-wrapper .setup-wrapper{width:100%;max-width:750px}.oauth-wrapper .setup-wrapper .oauth-heading{max-width:60%}.oauth-wrapper .setup-wrapper .oauth-permissions-details{background-color:#fff}.oauth-wrapper .setup-wrapper.oauth-restriction-wrapper{padding-top:0}.oauth-connection-illustration{float:right;max-width:170px;margin:0 auto}.oauth-connection-illustration .oauth-image{float:left;padding:5px;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.1);border-radius:6px}.oauth-connection-illustration .oauth-image img{display:block;width:75px;height:75px;border-radius:3px}.oauth-connection-illustration .oauth-image.oauth-image-user{margin-top:20px;margin-left:-20px}.oauth-review-wrapper .oauth-review-permissions{max-width:426px}.oauth-review-wrapper .setup-info-module{width:300px;border-color:#e2e2e2;box-shadow:none}.oauth-review-wrapper .no-description{color:#767676}.oauth-review-wrapper .features-list{padding-bottom:0}.oauth-split{float:none}@media (max-width: 767px){.oauth-split h1{font-size:24px}}@media (max-width: 767px){.oauth-split h3{font-size:16px}}@media (max-width: 767px){.oauth-split .column{display:block;float:none;margin:0 auto}}@media (max-width: 767px){.oauth-split .oauth-heading{min-width:80%;text-align:center}}@media (max-width: 767px){.oauth-split .oauth-review-wrapper .oauth-review-permissions{max-width:none}}@media (max-width: 767px){.oauth-split .oauth-review-wrapper .setup-info-module{float:none;width:100%;margin:0 auto}}@media (max-width: 767px){.oauth-split .oauth-review-wrapper .btn-primary{width:100%}}.ellipsis-button{display:inline-block;height:12px;padding:0 5px;margin-left:2px;font-size:12px;font-weight:bold;line-height:6px;color:#555;text-decoration:none;vertical-align:middle;background-color:#ddd;border-radius:1px}.ellipsis-button:hover{text-decoration:none;background-color:#ccc}.ellipsis-button::before{content:"\2026"}.oauth-permissions-details{position:relative;padding:15px;margin:0;list-style:none;border-bottom:1px solid #f2f2f2}.oauth-permissions-details:first-child{border-radius:3px 3px 0 0}.oauth-permissions-details:last-child{border:0;border-radius:0 0 3px 3px}.oauth-permissions-details.oauth-public-data-only{border-radius:3px}.oauth-permissions-details .markdown-body{font-size:13px}.oauth-permissions-details .content{display:none;margin-left:45px}.oauth-permissions-details .content .form-checkbox{margin-left:0}.oauth-permissions-details .content .form-checkbox:last-child{margin-bottom:0}.oauth-permissions-details .octicon{float:left;color:#767676;text-align:center}.oauth-permissions-details .permission-help{font-size:13px}.oauth-permissions-details .permission-help ul{padding-left:20px;margin:1em 0}.oauth-permissions-details .permission-summary{margin-left:45px}.oauth-permissions-details .permission-summary .access-details{position:relative;color:#767676}.oauth-permissions-details .permission-summary em.highlight{position:relative;padding:2px 3px;margin-right:-2px;margin-left:-3px;font-style:normal;color:#4c4a42;background:#fff9ea;border-radius:3px}.oauth-permissions-details .permission-title{display:block;color:#000}.oauth-permissions-details a.btn-sm{float:right;margin-top:4px}.oauth-permissions-details.open a.btn-sm{background-color:#dcdcdc;background-image:none;border-color:#b5b5b5;box-shadow:inset 0 2px 4px rgba(0,0,0,0.15)}.oauth-permissions-details.open .content{display:block}.oauth-permissions-details.default:not(.delete) .no-access,.oauth-permissions-details.default:not(.delete) .default-access,.oauth-permissions-details.none .no-access,.oauth-permissions-details.none .default-access{display:inline}.oauth-permissions-details.default:not(.delete) .access-details,.oauth-permissions-details.default:not(.delete) .permission-title,.oauth-permissions-details.none .access-details,.oauth-permissions-details.none .permission-title{color:#999}.oauth-permissions-details.default:not(.delete) .octicon,.oauth-permissions-details.none .octicon{color:#ccc}.oauth-permissions-details.default .default-access{display:inline}.oauth-permissions-details.full .full-access{display:inline}.oauth-details-toggle{position:absolute;top:0;right:0;padding:20px 15px}.oauth-details-toggle .octicon-chevron-up{display:none}.open .oauth-details-toggle .octicon-chevron-down{display:none}.open .oauth-details-toggle .octicon-chevron-up{display:block}.oauth-user-permissions .full-access,.oauth-user-permissions .limited-access,.oauth-user-permissions .limited-access-none,.oauth-user-permissions .limited-access-followers,.oauth-user-permissions .limited-access-emails,.oauth-user-permissions .no-access{display:none}.oauth-user-permissions.limited .limited-access-none{display:inline}.oauth-user-permissions.limited.limited-email .limited-access,.oauth-user-permissions.limited.limited-email .limited-access-none{display:none}.oauth-user-permissions.limited.limited-email .limited-access-emails{display:inline}.oauth-user-permissions.limited.limited-email.limited-follow .limited-access{display:inline}.oauth-user-permissions.limited.limited-email.limited-follow .limited-access-none,.oauth-user-permissions.limited.limited-email.limited-follow .limited-access-emails,.oauth-user-permissions.limited.limited-email.limited-follow .limited-access-followers{display:none}.oauth-user-permissions.limited.limited-follow .limited-access,.oauth-user-permissions.limited.limited-follow .limited-access-none{display:none}.oauth-user-permissions.limited.limited-follow .limited-access-followers{display:inline}.oauth-repo-permissions .default-access,.oauth-repo-permissions .public-access,.oauth-repo-permissions .full-access{display:none}.oauth-repo-permissions.public .public-access{display:inline}.oauth-delete-repo-permissions .octicon-alert{color:#bd2c00}.oauth-repo-status-permissions .no-access,.oauth-repo-status-permissions .full-access,.oauth-repo-deployment-permissions .no-access,.oauth-repo-deployment-permissions .full-access{display:none}.oauth-notifications-permissions .no-access,.oauth-notifications-permissions .read-access,.oauth-notifications-permissions .via-public-access,.oauth-notifications-permissions .via-full-access{display:none}.oauth-notifications-permissions.read .read-access{display:inline}.oauth-notifications-permissions.via-public .via-public-access{display:inline}.oauth-notifications-permissions.via-public .octicon{display:none}.oauth-notifications-permissions.via-full .via-full-access{display:inline}.oauth-gist-permissions .no-access,.oauth-gist-permissions .full-access{display:none}.oauth-granular-permissions .no-access,.oauth-granular-permissions .read-access,.oauth-granular-permissions .write-access,.oauth-granular-permissions .full-access{display:none}.oauth-granular-permissions.none .no-access{display:inline}.oauth-granular-permissions.read .read-access{display:inline}.oauth-granular-permissions.write .write-access{display:inline}.oauth-granular-permissions.full .full-access{display:inline}.oauth-no-description{color:#767676}.oauth-org-access-details a:hover{text-decoration:none}.oauth-org-access-details .boxed-group-inner{border:0;border-radius:3px}.oauth-org-access-details .boxed-group-list>li{line-height:24px}.oauth-org-access-details .boxed-group-list>li:first-child{border-radius:3px 3px 0 0}.oauth-org-access-details .boxed-group-list>li .loading-indicator{display:none;margin:4px}.oauth-org-access-details .boxed-group-list>li.on{background:#fff}.oauth-org-access-details .boxed-group-list>li.on:hover{background:#ffe}.oauth-org-access-details .boxed-group-list>li.on .authorized-tools{display:block}.oauth-org-access-details .boxed-group-list>li.on .unauthorized-tools{display:none}.oauth-org-access-details .boxed-group-list>li.on strong{color:#333}.oauth-org-access-details .boxed-group-list>li.on .octicon-check{display:inline}.oauth-org-access-details .boxed-group-list>li.on .octicon-x{display:none}.oauth-org-access-details .boxed-group-list>li.loading .unauthorized-tools,.oauth-org-access-details .boxed-group-list>li.loading .authorized-tools{display:none}.oauth-org-access-details .boxed-group-list>li.loading .loading-indicator{display:block}.oauth-org-access-details .boxed-group-list>li .authorized-tools{display:none}.oauth-org-access-details .boxed-group-list>li .unauthorized-tools{display:block}.oauth-org-access-details .btn{line-height:1.5em}.oauth-org-access-details .octicon{color:#979797}.oauth-org-access-details .octicon-check{display:none;color:#6cc644}.oauth-org-access-details .octicon-x{display:inline}.oauth-org-access-details .octicon-x.org-access-denied{color:#bd2c00}.added-permission{color:#6cc644}.permission-title{margin-top:0}.oauth-application-whitelist h2{display:inline-block}.oauth-application-whitelist .request-info{display:block}.oauth-application-whitelist .request-info strong{display:inline-block;color:#333}.oauth-application-whitelist .request-info .application-description{display:none}.oauth-application-whitelist .request-info.open .application-description{display:block}.oauth-application-whitelist .avatar{margin-top:0}.oauth-application-whitelist .requestor{font-weight:bold}.oauth-application-whitelist .octicon-alert{color:#c9510c}.oauth-application-whitelist .octicon-check,.oauth-application-whitelist .approved-request{color:#6cc644}.oauth-application-whitelist .denied-request{color:#bd2c00}.oauth-application-whitelist .request-indicator{margin-left:10px}.oauth-application-whitelist .edit-link{color:#999}.oauth-application-whitelist .edit-link:hover{color:#4078c0}.oauth-application-whitelist .boxed-group-list{margin-top:1em}.oauth-application-whitelist .boxed-group-list li{padding:10px}.boxed-group-inner .oauth-application-info{margin-bottom:10px}.oauth-application-info .application-title{font-size:30px;color:#333}.oauth-application-info .application-description{margin-top:3px;margin-bottom:0}.oauth-application-info .app-info{display:inline-block;margin-right:10px;color:#999}.oauth-application-info .app-info .octicon{margin-right:5px}.oauth-application-info .listgroup.listgroup-padded .listgroup-item{min-height:40px;padding:15px}.oauth-application-info .listgroup-item{line-height:inherit}.oauth-application-info .subhead.subhead-nude{margin-bottom:4px;border-bottom-width:0 !important}.oauth-application-info .meta-link{color:#999}.oauth-application-info .meta-link:hover{color:#4078c0}.oauth-application-info .app-denied,.oauth-application-info .app-approved{margin-left:10px;font-size:13px;font-weight:normal;white-space:nowrap}.oauth-application-info .app-approved,.oauth-application-info .octicon-check{color:#6cc644}.oauth-application-info .app-denied,.oauth-application-info .octicon-x{color:#c9510c}.restrict-oauth-access-button{margin-right:20px}.restrict-oauth-access-info{margin-bottom:40px;font-size:15px}.restrict-oauth-access-list{padding-left:25px}.restrict-oauth-access-list li{margin-bottom:10px}.restrict-oauth-access-list li:last-child{margin-bottom:0}.app-transfer-actions form{display:inline}.oauth-border{border-bottom:1px solid #e5e5e5}.oauth-border:last-child{border:0}.developer-app-item .developer-app-avatar-cell{width:60px}.developer-app-item .developer-app-name{font-size:15px;font-weight:bold;line-height:1.2;color:#333}.developer-app-item .developer-app-name:hover{color:#4078c0;text-decoration:none}.developer-app-item .developer-app-info-cell{padding-left:0}.developer-app-item .developer-app-list-meta{margin-top:3px;margin-bottom:2px;font-weight:normal;color:#999}.org-transfer-requests{margin:10px 0 20px}.invitation-container{width:600px;padding:20px;margin:40px auto;border:1px solid #ddd;border-radius:3px}.invitation-container h3{font-size:16px;font-weight:normal}.invitation-disclosure{position:relative;padding:10px 0 10px 24px;color:#767676;text-align:center;list-style:none}.invitation-disclosure .octicon{display:inline-block;margin-right:5px;color:#767676;text-align:center}.invitation-header{position:relative;text-align:center}.invitation-header .avatar{margin-bottom:20px}.invitation-header .invitation-title{margin:0;font-size:18px;font-weight:normal;line-height:16px}.invitation-header .inviter{margin:5px 0 10px;font-size:13px;color:#767676}.invitation-footer{margin:40px 0 20px}.invitation-footer form{display:inline-block;margin-right:10px}.orghead{padding-top:20px;padding-bottom:0;margin-bottom:20px;color:#666;background-color:#fafafa;border-bottom:1px solid #eee}.orghead .edit-org{position:relative;top:-6px;display:inline-block;padding:3px 5px;font-size:14px;color:#aaa;border:1px solid #e5e5e5;border-radius:3px}.orghead .edit-org:hover{color:#4078c0;background-color:#fff}.orghead .edit-org .octicon{font-size:14px}.orghead .orgnav{position:relative;top:1px;margin-top:10px}.org-header-wrapper .avatar{display:block;width:100px}.org-header-wrapper .flex-table-item-primary{padding-left:20px;white-space:normal}.org-name{margin-top:0;margin-bottom:5px;font-size:36px;font-weight:normal;color:#333}.org-description{margin-top:0;margin-bottom:8px;font-size:16px;line-height:1.25}.org-header-meta{font-size:12px;line-height:1.5;list-style:none}.org-header-meta .meta-item{display:inline-block;max-width:100%;padding-right:18px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.org-header-meta .meta-item .meta-link{color:#666}.org-header-meta .octicon{position:relative;top:1px;margin-right:2px;color:#ccc;vertical-align:text-bottom}.org-header-meta.has-email.has-blog .meta-item,.org-header-meta.has-email.has-location .meta-item,.org-header-meta.has-blog.has-location .meta-item{max-width:278px}.org-header-meta.has-email.has-blog.has-location .meta-item{max-width:186px}.org-link{color:#333}.org-link:hover{color:#4078c0;text-decoration:none}.org-main{float:left;width:640px}.org-sidebar{float:right;width:280px}.org-sidebar .member-badge{display:block;padding-top:0;padding-bottom:0;border-top:0}.audit-log-map-container{position:relative;margin-bottom:20px}.audit-log-map-container .activity{position:absolute;top:120px;left:340px;z-index:99999;display:none;text-align:center}.audit-log-map-container .is-graph-loading .activity{display:block}.audit-search-form{position:relative;margin-bottom:20px}.audit-search-form::before{display:table;content:""}.audit-search-form::after{display:table;clear:both;content:""}.audit-search-form .subnav-search-input{width:250px}.audit-search-form .audit-log-search-query{width:654px}.audit-log-map{height:277px;overflow:hidden;background-color:#4078c0;border-radius:3px;box-shadow:inset 1px 1px 0 rgba(0,0,0,0.2)}.map-background{pointer-events:all;fill:#4078c0;cursor:grab}.land{fill:none;stroke:#256aae;stroke-width:2;shape-rendering:crispedges}.country{fill:#d7c7ad;shape-rendering:crispedges;cursor:pointer}.country.hk{stroke:#a5967e}.country:hover{fill:#c8b28e}.country.active{fill:#f6e5ca}.borders{fill:none;stroke:#a5967e;shape-rendering:crispedges}.graticule{pointer-events:none;fill:none;stroke:#fff;stroke-opacity:0.2;shape-rendering:crispedges}.graticule :nth-child(2n){stroke-dasharray:2, 2}.security-map-legend circle{fill-opacity:0;stroke:#fff;stroke-width:1.5}.security-map-legend text{font-size:10px;fill:#fff;text-anchor:end}.security-map-legend .link{stroke:#fff;stroke-width:1.5}.audit-point{pointer-events:none;fill:#bd2c00;fill-opacity:0.8;stroke:#bd2c00}.country-info{position:absolute;top:10px;right:10px;padding:10px;pointer-events:none;background:rgba(255,255,255,0.9);border-radius:2px;opacity:0}.audit-log-search .member-info{width:300px}.audit-log-search .member-info .member-avatar{float:left;margin-right:15px}.audit-log-search .member-info .member-link{display:block}.audit-log-search .member-info .member-list-avatar{margin-right:0}.audit-log-search .member-info .ghost{display:inline-block;color:#767676}.audit-log-search .blankslate{border-top-left-radius:0;border-top-right-radius:0}.audit-log-search .export-phrase{margin:5px 0}.audit-results-header{padding:10px;background-color:#f7f7f7;border:1px solid #dcdcdc;border-bottom:0;border-top-left-radius:3px;border-top-right-radius:3px}.audit-results-header::before{display:table;content:""}.audit-results-header::after{display:table;clear:both;content:""}.audit-results-header-title{float:left;margin:3px 0;font-size:14px;font-weight:normal;color:#333}.audit-results-actions{overflow:auto}.audit-search-clear{float:left;margin-bottom:20px;border:0}.audit-search-clear .issues-reset-query{margin-bottom:0}.audit-type{width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.audit-type .octicon{margin-right:3px;font-weight:normal;vertical-align:bottom}.audit-type .repo{color:#c9510c}.audit-type .team{color:#6cc644}.audit-type .user{color:#6e5494}.audit-type .oauth_access{color:#bd2c00}.audit-type .hook{color:#e1bf4e}.export-actions{display:inline-block;margin-left:10px}.export-actions a{margin-top:-3px;color:#999}.export-actions a:hover{color:#4078c0;text-decoration:none}.export-actions .select-menu-button::after{position:absolute;top:50%;right:15px;margin-top:-2px}.export-actions .select-menu-modal{width:111px}.export-actions .select-menu-item-text{text-align:center}.export-phrase{margin-top:5px}.export-phrase pre{padding-left:10px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;white-space:pre-wrap;border-left:1px solid #eee}.audit-log-export-button{width:110px;height:30px;-webkit-transition:0.25s width ease-in-out;transition:0.25s width ease-in-out}.audit-log-export-button .loader{position:absolute;top:50%;left:11px;display:none;margin-top:-9px}.audit-log-export-button .octicon{position:absolute;top:50%;left:11px;margin-top:-9px}.audit-log-export-button .audit-log-export-status{position:absolute;top:4px;left:35px}.audit-log-export-button.disabled{width:125px}.audit-log-export-button.disabled::after{display:none}.audit-log-export-button.disabled .octicon{display:none}.audit-log-export-button.disabled .loader{display:block}.org-module-title{margin:-15px -15px 0;font-size:18px;border-bottom:1px solid #eee}.org-module-link{display:block;padding:15px;color:#333}.org-module-link:hover,.org-module-link:hover .org-stats{color:#4078c0;text-decoration:none}.org-stats{float:right;margin-top:3px;font-size:14px;color:#767676}.org-members-title{margin-bottom:0;border-bottom:0}.member-avatar-group{margin:-1px}.member-avatar-group::before{display:table;content:""}.member-avatar-group::after{display:table;clear:both;content:""}.member-avatar{float:left;margin:1px}.member-row{display:block;min-height:40px;padding-bottom:15px;margin-top:15px;margin-bottom:15px;font-size:14px;color:#333;border-bottom:1px solid #eee}.member-row::before{display:table;content:""}.member-row::after{display:table;clear:both;content:""}.member-row:hover{color:#4078c0;text-decoration:none}.member-row:last-child{padding-bottom:0;margin-bottom:0;border-bottom:0}.member-row .avatar{float:left}.member-row .member-name{display:block;padding-left:50px}.member-row .member-fullname{display:block;padding-left:50px;word-wrap:break-word}.member-fullname{color:#767676}.org-no-members{margin-top:20px;margin-bottom:10px;color:#767676;text-align:center}.org .no-results{padding:10px;color:#767676}.org-toolbar.disabled{pointer-events:none}.org-toolbar .subnav-search{width:320px;margin-left:0}.org-toolbar .subnav-search-context+.subnav-search{margin-left:-1px}.org-toolbar .subnav-search-input{width:100%}.pending-invitations-link,.buy-more-seats-link{padding-right:15px;padding-left:15px}.auto-search-group{position:relative}.auto-search-group .auto-search-input{padding-left:30px}.auto-search-group .spinner,.auto-search-group>.octicon{position:absolute;left:10px;z-index:5;width:16px;height:16px}.auto-search-group .spinner{top:9px;background-color:#fff}.auto-search-group>.octicon{top:10px;font-size:14px;color:#bbb;text-align:center}.org-list .list-item{position:relative;padding-top:15px;padding-bottom:15px;border-bottom:1px solid #eee}.org-list .list-item::before{display:table;content:""}.org-list .list-item::after{display:table;clear:both;content:""}.org-list .cancel-link{color:#767676}.org-repos .blankslate,.org-team-main .blankslate{margin-top:15px}.org-repos-mini{padding:0;margin:0}.org-repos-mini .org-repo-mini-item:first-child .org-repo-mini-cell{border-top:0}.org-repos-mini .org-repo-icon{vertical-align:middle}.org-repos-mini .org-repo-name{margin-top:0;margin-bottom:0;font-size:14px;word-wrap:break-word}.org-repos-mini .org-repo-name .octicon-repo{color:#767676}.org-repos-mini .org-repo-name .octicon-lock{color:#e9dba5}.org-repos-mini .org-repo-name .repo-prefix{font-weight:normal}.org-repos-mini .org-repo-name .repo-slash{display:inline-block;margin-right:-4px;margin-left:-4px}.org-repos-mini .org-repo-forked{display:inline-block;max-width:270px;margin-top:0;margin-bottom:0;font-size:12px;font-weight:normal}.org-repo-mini-cell{padding-top:15px;padding-bottom:15px;vertical-align:middle}.org-repo-meta{width:165px}.org-repo-meta .access-level{cursor:default}.org-repo-access-level{text-align:center}.org-repo-manage{width:270px}.org-repo-higher-access{display:none;margin-top:2px;margin-left:16px;font-size:11px}.org-higher-access-member .manage-access{position:relative;top:2px;font-size:12px}.with-higher-access .org-repo-higher-access{display:block}.with-higher-access .table-list-cell-checkbox{vertical-align:top}.permission-level-cell .select-menu-button{width:78px;text-align:left}.permission-level-cell .select-menu-button::after{position:absolute;top:10px;right:10px}.permission-level-cell .spinner,.permission-level-cell .permission-success-icon{position:absolute;display:inline-block;margin-left:15px;opacity:0;-webkit-transition:opacity 0.2s ease-in-out;transition:opacity 0.2s ease-in-out}.permission-level-cell .permission-success-icon{margin-top:4px;color:#6cc644}.permission-level-cell .is-loading .spinner,.permission-level-cell .was-successful .permission-success-icon{opacity:1}.org-repo-permission-select .select-menu-modal .description{padding-right:20px}.org-repo-permission-select .select-menu-option-title{margin-top:0;margin-bottom:0}.org-repo-permission-select .navigation-focus .select-menu-option-title{color:#fff}.reinstate-org-member{position:relative;width:500px;margin:40px auto}.reinstate-org-member .reinstate-lead{margin-bottom:30px;font-size:16px}.reinstate-org-member .reinstate-detail-container{margin:15px 0}.reinstate-org-member .reinstate-title{font-weight:bold;color:#333}.reinstate-org-member .reinstate-title .octicon{width:16px;margin-right:10px}.facebox .reinstate-data-list{max-height:168px;padding-left:0;margin-bottom:0;margin-left:0;overflow:auto}.add-member-wrapper{position:relative;width:500px;margin:40px auto}.add-member-wrapper .available-seats{color:#767676}.add-member-wrapper .buy-more-link{margin-right:5px}.add-member-wrapper .send-invitation-button{float:none}.add-member-lead{font-size:16px}.add-member-roles{margin:30px 0}.add-member-roles .role-make-member{display:block;margin-bottom:10px}.add-member-team-list{display:block;margin-bottom:30px;list-style:none}.add-member-team-list .team-row-header{padding:10px 20px 10px 0;font-size:12px;text-align:right;background:#fafafa;border-top-left-radius:3px;border-top-right-radius:3px}.add-member-team-list .team-row-header .team-row-teams{margin-right:245px}.add-member-team-list .team-row-header .team-row-members{margin-right:35px}.add-member-team-list .team{display:block;font-weight:normal;cursor:pointer}.add-member-team-list .team:first-child{border-top:1px solid #f2f2f2}.add-member-team-list .team .btn-sm{float:right}.add-member-team-list .team-info{max-width:80%;color:#000;text-decoration:none}.add-member-team-list .team-meta{margin-top:2px;margin-bottom:2px;color:#767676}.add-member-team-list .team-description{width:260px;margin-top:2px;margin-bottom:2px;color:#333}.add-member-team-list .team-toggler .turn-on{display:inline-block}.add-member-team-list .team-toggler.on .turn-off{display:inline-block}.add-member-team-list .team-toggler .turn-off{display:none}.add-member-team-list .team-toggler.on .turn-on{display:none}.team-list-footer{padding:10px 0;text-align:center;border-top:1px solid #e5e5e5}.team-list-footer .show-all-link .octicon{margin-left:5px;color:#767676}.invite-team-member-list .team{cursor:default}.invite-team-member-list .team:first-child .table-list-cell{border-top:0}.invite-team-member-list .table-list-cell-checkbox{width:42px}.invite-team-member-list .team-toggler{padding-top:12px}.invite-team-member-list .team-info{width:260px;padding:10px 15px 10px 0}.invite-team-member-list .team-description{display:block;padding-top:0;padding-bottom:0;font-weight:normal}.invite-team-member-list .team-meta{width:100px;text-align:left;vertical-align:middle}.invite-team-member-list .team-link{color:#4078c0;text-align:right}.org-people-blankslate{margin-top:-20px;border-top-width:0;border-top-left-radius:0;border-top-right-radius:0}.migration-jumbotron{height:70vh;min-height:450px;max-height:650px}.migration-jumbotron,.migration-sub-header{position:relative;margin-top:-1px;background-color:#3f4851;background-image:-webkit-linear-gradient(#3f4851 0, #282d33 100%);background-image:linear-gradient(#3f4851 0, #282d33 100%)}.migration-jumbotron::after,.migration-sub-header::after{position:absolute;top:0;right:0;bottom:0;left:0;z-index:1;content:" ";background-image:url("/images/modules/orgs/dots-bg.png");background-repeat:repeat;background-size:80%;opacity:0.75}.migration-jumbotron-content{position:relative;top:50%;z-index:2;width:980px;padding:100px 60px;margin:0 auto;text-align:center;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.migration-jumbotron-octicons{height:60px;margin-bottom:20px;text-align:center}.migration-jumbotron-octicon-item{position:relative;display:inline-block;width:60px;height:60px;margin-right:10px;margin-left:10px;background-image:-webkit-linear-gradient(135deg, #6e5494 30%, #c9510c 100%);background-image:linear-gradient(-45deg, #6e5494 30%, #c9510c 100%);border-radius:50px}.migration-jumbotron-octicon-item::after{position:absolute;top:1px;right:1px;bottom:1px;left:1px;content:"";background-color:#383f47;border-radius:50px}.migration-jumbotron-octicon-item .octicon{position:relative;z-index:2;margin-top:16px;color:rgba(255,255,255,0.9)}.migration-jumbotron-title,.migration-jumbotron-lead,.migration-sub-title,.migration-sub-lead{color:#fff;text-shadow:0 1px 1px rgba(0,0,0,0.05)}.migration-jumbotron-title,.migration-section-title{margin-bottom:10px;font-weight:300}.migration-jumbotron-title{font-size:40px}.migration-section-title{margin-top:0;font-size:30px}.migration-jumbotron-lead{margin-top:0;font-size:24px;opacity:0.85}.migration-section-lead{margin-top:20px;margin-bottom:20px}.migration-jumbotron-btn{padding:12px 18px;font-size:16px;color:#6e5494;background-color:#fff;border-width:0;box-shadow:0 3px 3px rgba(0,0,0,0.05)}.migration-jumbotron-btn:hover{color:#6e5494;background-color:#eee}.migration-section{padding-top:100px;padding-bottom:100px;overflow:hidden;border-bottom:1px solid #ddd}.migration-feature-list{margin-top:30px;margin-bottom:20px;overflow:hidden;font-size:14px;color:#767676;list-style:none}.migration-feature-list::before{display:block;width:100px;margin-bottom:30px;content:"";border-top:1px solid #ddd}.migration-feature-list .octicon{width:22px;margin-left:-3px;color:#767676;text-align:center}.migration-feature-list-item{float:left;width:50%;margin-bottom:15px}.migration-section-grey{background-color:#fcfcfc}.migration-illustration-wrapper::before{display:table;content:""}.migration-illustration-wrapper::after{display:table;clear:both;content:""}.migration-illustration{width:700px;margin-top:-30px;margin-bottom:-50px;border:1px solid #ddd;border-radius:3px;box-shadow:0 0 15px rgba(0,0,0,0.05)}.migration-illustration-left{float:right;margin-right:50px}.migration-illustration-right{float:left;margin-left:50px}.migration-section-privileges{padding-top:80px;padding-bottom:80px}.migration-footer{position:relative;z-index:1;padding-top:60px;padding-bottom:60px;margin-bottom:-41px;border-bottom:1px solid #ddd}.migration-footer-content{width:800px;margin:0 auto;text-align:center}.migration-footer-title,.migration-footer-lead{margin-bottom:0}.migration-footer-lead{margin-top:10px}.migration-footer-btn{margin-top:20px}.migration-sub-header{padding-top:40px;padding-bottom:40px;margin-bottom:40px}.org-settings-updating{padding:15px;margin-top:0;margin-bottom:30px;background-color:#fff;border:1px solid #ddd;border-radius:3px}.org-settings-updating .spinner{display:inline-block;margin-top:-2px;vertical-align:middle}.org-disabled-settings{pointer-events:none;opacity:0.5}.migration-sub-header-content{width:68%}.migration-sub-title{margin-bottom:0}.migration-sub-lead{margin-top:10px;margin-bottom:0}.migration-org-avatar{margin-top:6px;margin-right:72px;border:3px solid #fff;border-radius:3px}.org-migration-settings-sidebar .migrate-org-roles{margin-top:0;margin-bottom:10px}.org-migration-settings-sidebar .preserve-member-privileges-btn{display:none}.org-migration-settings-sidebar .member-privilege-radios-preserved .preserve-member-privileges-btn{display:block}.org-migration-settings-sidebar .member-privilege-radios-preserved .save-member-privileges-btn{display:none}.org-migration-settings-section{position:relative;padding-bottom:50px;margin-right:60px;margin-bottom:50px;border-bottom:1px solid #ddd}.org-migration-settings-section:last-child{padding-bottom:0;margin-bottom:0;border-bottom:0}.org-migration-settings-section .disabled{pointer-events:none;opacity:0.5}.org-migration-settings-section .spinner{display:inline-block;margin-bottom:-3px}.org-migration-settings-icon{position:absolute;left:-45px;color:#ccc}.org-migration-settings-title{margin-bottom:0;font-size:22px;font-weight:normal}.org-migration-settings-info{margin-top:5px;margin-bottom:30px;font-size:16px;color:#767676}.migrate-owners-wrapper{position:relative;min-height:550px}.migrate-owners-content-about,.migrate-owners-content-rename{position:absolute;top:0;left:50%;margin:30px auto 0;-webkit-transition:opacity 0.2s ease-in-out, -webkit-transform 0.3s ease-in-out;transition:opacity 0.2s ease-in-out, transform 0.3s ease-in-out;-webkit-transform:translate(-50%, 0);transform:translate(-50%, 0)}.migrate-owners-content-hidden{z-index:20;pointer-events:none;opacity:0;-webkit-transform:translate(-50%, 150px);transform:translate(-50%, 150px)}.migrate-owners-content-about{width:700px;text-align:center}.migrate-owners-title{font-size:35px;font-weight:normal}.migrate-owners-lead{margin-top:0;margin-bottom:20px}.migrate-owners-content-rename{width:520px}.rename-owners-error span{display:inline-block;padding:5px;margin-bottom:10px;font-size:11px;font-weight:bold;color:#494620;background:#f7ea57;border:1px solid #c0b536;border-top-color:#fff;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.rename-owners-spinner{position:absolute;top:30px;right:30px}.delete-owners-button{color:#767676}.delete-owners-button:hover{color:#bd2c00}.rename-owners-team-form .rename-owners-team-input{font-size:22px;font-weight:bold}.rename-owners-team-form .note{margin-top:5px;margin-bottom:15px;color:#767676}.migrate-org-roles{display:table;width:100%;margin-top:-20px;border:1px solid #ddd;border-radius:3px}.migrate-org-roles .tooltipped::after{width:150px;white-space:normal}.no-avatars-roles-matrix .migrate-org-roles{margin-top:5px}.migrate-org-roles-item{display:table-cell;width:33.33%;border-right:1px solid #ddd}.migrate-org-roles-item:last-child{border-right:0}.migrate-ability-list{margin:15px 0;list-style:none}.migrate-ability-list-item{padding-top:5px;padding-bottom:5px;margin:0 20px;font-size:14px}.migrate-ability-list-item:first-child{border-top:0}.migrate-ability-list-item .octicon-check,.migrate-ability-list-item .octicon-x{width:15px}.migrate-ability-list-item .octicon-check{color:#6cc644}.migrate-ability-list-item .octicon-x{color:#aaa}.migrate-ability-list-item .octicon-question{font-size:12px;color:#555}.migrate-ability-not-possible{color:#767676}.default-repository-permission .octicon-x,.members-can-create-repositories .octicon-x,.team-privacy .octicon-x{display:none}.default-repository-permission.migrate-ability-not-possible .octicon-x,.members-can-create-repositories.migrate-ability-not-possible .octicon-x,.team-privacy.migrate-ability-not-possible .octicon-x{display:inline-block}.default-repository-permission.migrate-ability-not-possible .octicon-check,.members-can-create-repositories.migrate-ability-not-possible .octicon-check,.team-privacy.migrate-ability-not-possible .octicon-check{display:none}.migrate-org-roles-header{padding:15px 20px;border-bottom:1px solid #ddd}.migrate-org-roles-title{margin-top:0;margin-bottom:0;font-size:18px;font-weight:normal}.migrate-org-roles-lead{margin-top:4px;margin-bottom:0;font-size:14px;color:#767676}.migrate-org-roles-count{padding:10px 20px;color:#767676;border-top:1px solid #ddd}.migrate-org-avatar-list{margin-top:5px;margin-bottom:10px}.migrate-org-avatar-list::before{display:table;content:""}.migrate-org-avatar-list::after{display:table;clear:both;content:""}.migrate-org-avatar-list .migrate-org-avatar,.migrate-org-avatar-list .migrate-org-avatar-empty{float:left;margin-left:2px}.migrate-org-avatar-list .migrate-org-avatar:first-child,.migrate-org-avatar-list .migrate-org-avatar-empty:first-child{margin-left:0}.migrate-org-avatar-list .migrate-org-avatar-empty{width:30px;height:30px;border-radius:3px}.migrate-org-avatar-list .migrate-org-more-ellipsis,.migrate-org-avatar-list .migrate-org-zero{font-size:18px;line-height:30px;color:#767676;text-align:center}.migrate-org-avatar-list .migrate-org-more-ellipsis{font-weight:bold;line-height:20px;background-color:#f5f5f5}.migrate-org-avatar-list .migrate-org-zero{color:#767676;border:1px dashed #ddd}.migrate-org-avatar-list .migrate-org-more-ellipsis:hover{text-decoration:none}.migrate-org-avatar-list .tooltipped::after{width:auto;white-space:nowrap}.default-permission-update-in-progress .form-group{pointer-events:none;opacity:0.5}.default-permission-update-in-progress .spinner{display:inline-block;margin-top:-3px;margin-left:5px;vertical-align:middle}.default-permission-updating{float:right;margin-top:10px;margin-right:10px}.default-permission-update-text{color:#767676}.org-settings-teams::before{display:table;content:""}.org-settings-teams::after{display:table;clear:both;content:""}.org-settings-team-item{float:left;width:50%;padding:20px 40px 25px;text-align:center}.org-settings-team-item:first-child{border-right:1px solid #ddd}.org-settings-team-count{font-size:30px;color:#000}.org-settings-team-type{margin-top:0;margin-bottom:10px;font-size:14px;font-weight:normal;color:#000}.org-settings-team-description{margin-top:0}.menu-item .org-avatar,.menu-item .org-octicon-credit-card{position:absolute}.menu-item .org-octicon-credit-card{right:0}.org-settings-link{display:block;padding:0 30px;word-wrap:break-word}.team-info-card{position:relative;margin-bottom:20px}.team-info-card .team-label-ldap{font-size:13px;line-height:32px}.team-info-card .team-description{margin-top:10px;font-size:14px;line-height:20px;color:#666;word-break:break-word}.team-info-card .team-description .link{color:#767676;cursor:pointer}.team-info-card .team-description .link:hover{text-decoration:underline}.team-info-card .description-toggler .turn-on{display:inline-block}.team-info-card .description-toggler.on .turn-off{display:inline-block}.team-info-card .description-toggler .turn-off{display:none}.team-info-card .description-toggler.on .turn-on{display:none}.team-title{margin-top:0;margin-bottom:0;font-size:22px;line-height:26px}.team-stats{padding-right:15px;padding-left:15px;margin-right:-15px;margin-bottom:-15px;margin-left:-15px;border-top:1px solid #eee}.stats-group{display:table;width:100%;table-layout:fixed}.stats-group-stat{display:table-cell;padding-top:10px;padding-bottom:10px;padding-left:15px;font-size:12px;color:#767676;text-transform:uppercase}.stats-group-stat:first-child{padding-left:0;border-right:1px solid #eee}.stats-group-stat:hover,.stats-group-stat:hover .stat-number{color:#4078c0;text-decoration:none}.stats-group-stat.no-link:hover{color:#767676;text-decoration:none}.stats-group-stat.no-link:hover .stat-number{color:#333}.stat-number{display:block;font-size:16px;color:#333}.team-description-form{width:100%;margin-top:10px;margin-bottom:20px}.team-description-field{width:100%;height:100px;margin-bottom:10px;font-size:14px}.team-actions .octicon{margin-right:0}.team-actions-form{display:inline-block}.org-team-sidebar{float:left;width:280px}.org-team-sidebar .team-note{font-size:13px;color:#767676;text-align:center}.org-team-sidebar .team-note .note-emphasis{color:#333}.legacy-admin-notice{margin-right:-15px;margin-bottom:-1px;margin-left:-15px}.legacy-admin-actions{margin-top:10px}.legacy-admin-migrate-btn{margin-right:5px}.org-team-main{float:right;width:660px}.permission-title{margin-top:0}.add-to-org-wrapper{width:500px}.add-to-org-wrapper form{padding-top:20px}.add-to-org-title{margin-bottom:0;font-size:30px;font-weight:normal}.owners-team-repo-note{margin-top:12px;margin-bottom:0}.owners-team-repo-note .octicon{font-size:14px}.team-member-list{list-style:none}.team-member-list .table-list-cell{padding-top:15px;padding-bottom:15px}.team-member-list .team-member-content{margin-left:50px}.team-member-list .team-member-username{margin:0;font-size:14px;font-weight:bold;line-height:20px}.team-member-list .team-member-description{margin:0;font-size:14px;line-height:20px;color:#767676}.team-member-list .label-admin,.team-member-list .label-generic{cursor:default}.team-member-list .manage-team-member{float:right}.team-member-list .manage-team-member .select-menu-modal{left:-176px;width:225px}.team-member-list .manage-team-member .select-menu-item.disabled{color:#bbb;cursor:not-allowed}.team-member-list .manage-team-member .select-menu-item.btn-link,.team-member-list .manage-team-member .select-menu-item .btn-link{width:100%;margin-left:0;color:#767676}.team-member-list .manage-team-member .select-menu-item.btn-danger,.team-member-list .manage-team-member .select-menu-item .btn-danger{color:#bd2c00}.team-member-list .manage-team-member .navigation-focus.disabled{color:#bbb;background-color:#fff}.team-member-list .manage-team-member .navigation-focus.btn-link,.team-member-list .manage-team-member .navigation-focus .btn-link{color:#fff;text-decoration:none}.team-member-list .manage-team-member .navigation-focus.btn-danger,.team-member-list .manage-team-member .navigation-focus .btn-danger{background:#bd2c00}.team-member-list-avatar{float:left;margin-right:10px}.org-team-form{width:440px;margin:0 auto}.org-team-form .disabled{opacity:0.5}.org-team-form .name-check-success,.org-team-form .name-check-fail{display:none}.org-team-form.is-name-check-success .name-check-success{display:inline}.org-team-form.is-name-check-fail .name-check-fail{display:inline}.org-validate-group{position:relative}.org-validate-group .octicon,.org-validate-group .spinner{position:absolute;top:9px;right:10px}.org-validate-group .octicon-check{color:#6cc644}.org-validate-group .octicon-alert{color:#bd2c00}.org-validate-group input::-ms-clear{display:none}.team-members{margin-bottom:20px}.confirm-removal-container .private-fork-count{margin-top:0;font-size:12px;font-weight:normal;color:#767676}.confirm-removal-container .deleting-private-forks-warning{position:relative;padding-left:26px}.confirm-removal-container .deleting-private-forks-warning .octicon{position:absolute;top:2px;left:0;color:#bd2c00}.confirm-removal-list-container{margin-bottom:15px;border:1px solid #eaeaea;border-radius:3px}.facebox .confirm-removal-list{max-height:182px;padding-left:0;margin-bottom:0;margin-left:0;overflow:auto}.confirm-removal-list-item{padding:10px;margin:0;font-size:14px;font-weight:bold;border-top:1px solid #eaeaea}.confirm-removal-list-item:first-child{border-top:0}.confirm-removal-list-item.cutoff-member-summary{font-weight:normal}.confirm-removal-team .octicon,.confirm-removal-repo .octicon{margin-right:3px;color:#767676}.convert-to-outside-collaborator-facebox{display:none}.org-blankslate{display:none}.org-section.is-empty .org-blankslate{display:block}.manage-user-info{padding-right:15px;padding-bottom:10px;padding-left:15px;margin-right:-15px;margin-left:-15px;border-bottom:1px solid #eee}.manage-user-info::before{display:table;content:""}.manage-user-info::after{display:table;clear:both;content:""}.manage-user-info .member-username{margin-top:0}.manage-user-info .member-username,.manage-user-info .member-fullname{display:block;overflow-x:hidden;text-overflow:ellipsis;white-space:nowrap}.manage-user-info .avatar{margin-top:2px;margin-right:10px}.manage-user-role{position:relative;padding-top:15px;padding-bottom:5px}.manage-user-role .select-menu-item-text .description{font-size:12px;line-height:16px}.manage-user-role .role-info{color:#767676}.manage-member-meta{list-style:none}.manage-member-meta-item{margin-top:12px;color:#767676}.manage-member-meta-item:first-child{margin-top:0}.manage-member-meta-item .btn-link{color:#767676}.manage-member-meta-item>.octicon{width:14px;margin-right:5px;color:#767676;text-align:center}.manage-member-meta-item>.octicon-alert{color:#c9510c}.member-two-factor-disabled{color:#bd2c00}.manage-member-button{margin-bottom:10px}.org-person-repo-header{margin-top:0}.org-person-repo-search{margin-top:5px;margin-right:5px}.org-user-notice-title{margin-top:0;margin-bottom:0}.org-user-notice-content{margin-top:10px;margin-bottom:10px;font-size:14px}.org-user-notice-content strong{color:#333}.org-user-notice-content:last-child{margin-bottom:0}.org-user-notice-content .octicon{color:#767676}.org-user-notice-icon{float:right;margin:10px 10px 20px;font-size:45px;color:#ccc}.org-migration-list{margin-bottom:20px;margin-left:20px;font-size:14px}.org-migration-list-item{margin-bottom:5px}.manage-repo-access-header{margin-top:30px;margin-bottom:30px}.manage-repo-access-header::before{display:table;content:""}.manage-repo-access-header::after{display:table;clear:both;content:""}.manage-repo-access-header .btn{margin-top:8px}.manage-repo-access-header .tooltipped::after{width:250px;white-space:normal}.manage-repo-access-heading{margin-top:-2px;margin-bottom:0;font-size:24px;font-weight:normal}.manage-repo-access-lead{margin-top:3px;margin-bottom:0;font-size:16px;color:#767676}.manage-repo-access-group{background-color:#fff;border:1px solid #ddd;border-radius:3px}.manage-repo-access-title{padding:12px 15px;margin-top:0;margin-bottom:0;font-size:14px;background-color:#f8f8f8;border-bottom:1px solid #ddd;border-radius:3px 3px 0 0}.manage-repo-access-wrapper{position:relative;padding-left:25px}.manage-repo-access-wrapper::before{position:absolute;top:15px;bottom:15px;left:20px;z-index:1;display:block;width:2px;content:"";background-color:#eee}.manage-repo-access-icon{position:relative;z-index:2;float:left;padding-top:2px;padding-bottom:2px;margin-top:-3px;margin-left:-25px;background:#fff}.manage-repo-access-icon .octicon{font-size:14px;color:#ccc}.manage-repo-access-list{list-style:none}.manage-repo-access-list-item{padding:15px}.manage-repo-access-list-item:last-child{border-bottom:0;border-radius:0 0 3px 3px}.manage-repo-access-teams-group{margin-top:-20px;list-style:none;border:1px solid #ddd;border-radius:3px}.manage-repo-access-team-item{border-top:1px solid #eee}.manage-repo-access-team-item:first-child{border-top:0}.manage-repo-access-description{margin-top:3px;margin-bottom:0;overflow:hidden;text-overflow:ellipsis;word-wrap:break-word;white-space:nowrap}.manage-repo-access-not-active{color:#333;background-color:#fafafa}.manage-repo-access-not-active .manage-repo-access-icon{background:#f9f9f9}.manage-access-remove-footer{padding:15px;border-top:1px solid #ddd}.manage-access-remove-footer .tooltipped::after{width:250px;white-space:normal}.manage-access-none{margin:20px 50px;text-align:center}.ldap-group-dn{display:block;font-weight:normal;color:#aaa}.ldap-import-groups-container .blankslate{display:none}.ldap-import-groups-container.is-empty .blankslate{display:block}.ldap-import-groups-container.is-empty .ldap-memberships-list{display:none}.ldap-import-groups-container .team-name-exists{position:absolute;z-index:1;display:none;padding:5px;font-size:11px;color:#494620;background:#f7ea57;border:1px solid #c0b536;border-top-color:#fff;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.ldap-import-groups-container .is-exists .ldap-mention-as{color:#bd2c00}.ldap-import-groups-container .is-exists .team-name-exists{display:inline-block}.ldap-memberships-list{margin-bottom:30px}.ldap-memberships-list .table-list-cell{padding-top:10px;padding-bottom:10px;font-size:13px;vertical-align:middle}.ldap-memberships-list .table-list-cell:last-child{width:92px}.ldap-memberships-list .team-name-exists{bottom:-19px;left:10px}.ldap-memberships-list .ldap-list-team-name{width:380px}.ldap-memberships-list .ldap-group-dn{font-size:11px}.ldap-memberships-list .ldap-mention-as{width:260px}.ldap-memberships-list .edit{position:absolute;padding:10px;margin-left:-33px;color:#4078c0;cursor:pointer}.ldap-memberships-list .edit-fields{display:none}.ldap-memberships-list .is-editing .edit-hide{display:none}.ldap-memberships-list .is-editing .edit-fields{display:block}.ldap-memberships-list .is-editing .spinner{margin-left:15px;vertical-align:middle}.ldap-memberships-list .is-removing{opacity:0.25}.ldap-memberships-list .is-removing .edit{opacity:0.5}.team-name-field{height:33px}.ldap-import-form-actions{margin-top:30px}.is-importing .team-ldap-group-adder-button .spinner{float:left}.team-ldap-group-adder{position:relative;float:left}.team-ldap-group-adder .team-name-exists{bottom:-27px;left:0}.team-ldap-group-adder .subnav-search-input{border-radius:4px 0 0 4px}.team-ldap-group-adder-button{width:90px;margin-left:-1px;border-radius:0 4px 4px 0}.team-ldap-group-adder-button .loading-indicator{display:none}.pending-invitations-facebox{display:none}.pending-team-invitations-link{display:block;padding-top:15px;padding-bottom:15px;margin-top:20px;border-top:1px solid #eee}.invited .team-member-list{margin:-10px 0 0}.invited .team-member-list .list-item{padding:10px 0;border-bottom:1px solid #eee}.invited .team-member-list .list-item::before{display:table;content:""}.invited .team-member-list .list-item::after{display:table;clear:both;content:""}.invited .team-member-list .list-item:last-of-type{border:0}.invited .team-member-list .list-item .edit-invitation{float:right;margin-top:6px}.invited-banner{padding:10px;margin-bottom:20px;background-color:#fff;border:1px solid #eaeaea;border-radius:4px}.invited-banner::before{display:table;content:""}.invited-banner::after{display:table;clear:both;content:""}.invited-banner .btn-sm{float:right;margin-top:-3px;margin-left:5px}.invited-banner p{margin:0;font-size:15px;color:#333}.invited-banner .inviter-link{font-weight:bold;color:#333}.org-user-block-input{width:275px}.editor-body-buffer{display:none}.pages-composer{padding-bottom:5px;margin-bottom:20px;border-bottom:1px solid #eee}.pages-composer label{display:inline-block;margin-bottom:10px;font-size:16px}.pages-composer input{margin-bottom:15px}.pages-composer p{margin-top:-10px;margin-bottom:10px;color:#767676}.pages-composer .gollum-editor-function-bar{margin-top:0}.pages-composer .gollum-editor{padding:0;margin:0;border:0}.pages-composer .gollum-editor-body{margin-top:10px}.gollum-readme{display:inline-block;margin-left:10px}.gollum-editor-function-bar .undo-load-readme{display:none}.theme-picker{margin-bottom:-1px;background-color:#fff;background-clip:padding-box;border-bottom:1px solid rgba(0,0,0,0.1);box-shadow:0 5px 10px rgba(0,0,0,0.1)}.theme-picker>.container{position:relative;overflow:hidden;text-align:center}.theme-picker-thumbs{border-bottom:1px solid #eee}.theme-picker-footer{position:relative;padding-bottom:15px}.theme-toggle{width:32px;height:32px;padding:0;color:#ccc;background:none;border:0}.theme-toggle:hover{color:#0084c8;text-decoration:none}.theme-toggle.disabled,.theme-toggle.disabled:hover{color:#ccc;cursor:not-allowed;opacity:0.3}.theme-toggle-full-left,.theme-toggle-full-right{position:absolute;top:50px;width:32px;height:32px;overflow:hidden}.theme-toggle-full-left{left:0}.theme-toggle-full-right{right:0}.theme-selector{height:102px;margin:15px 46px;overflow:hidden;white-space:nowrap}.theme-selector-thumbnail{display:inline-block;padding:2px;border:1px solid #ddd;border-radius:3px}.theme-selector-thumbnail+.theme-selector-thumbnail{margin-left:15px}.theme-selector-thumbnail:hover{text-decoration:none;background-color:#f5f5f5}.theme-selector-thumbnail.selected{padding:3px;background-color:#4078c0;border:0}.theme-selector-thumbnail.selected .theme-selector-img{border:1px solid #fff}.theme-selector-img{display:block;width:126px;height:96px;border-radius:1px}.theme-selector-name{display:none}.theme-picker-spinner{position:absolute;top:16px;left:50%;margin-left:-16px;background-color:#fff;opacity:0;-webkit-transition:0.2s, opacity ease-in-out;transition:0.2s, opacity ease-in-out}.theme-picker-spinner.visible{opacity:1}.theme-picker-spinner.visible ~ .theme-picker-controls .theme-name{opacity:0}.theme-selector-actions{padding-top:15px;text-align:right}.theme-selector-actions::before{display:table;content:""}.theme-selector-actions::after{display:table;clear:both;content:""}.theme-selector-actions .page-edit,.theme-selector-actions .page-publish{display:inline-block;margin-left:5px}.theme-picker-view-toggle{float:left}.theme-picker-view-toggle .for-hiding{display:none}.theme-picker-view-toggle.open .for-hiding{display:inline}.theme-picker-view-toggle.open .for-showing{display:none}.theme-picker-controls{position:absolute;top:15px;left:50%;display:none;width:220px;margin-left:-110px;line-height:34px;text-align:center}.theme-picker-controls .theme-toggle{vertical-align:middle}.theme-name{display:inline-block;margin-right:10px;margin-left:10px;font-size:20px;line-height:1;vertical-align:middle}.page-preview{position:relative;z-index:-100;display:block;width:100%;height:100%;padding:0;background-color:#fff;border:0}.pjax-loader-bar{position:fixed;top:0;left:0;z-index:40;opacity:0;-webkit-transition:opacity 0.4s linear 0.4s;transition:opacity 0.4s linear 0.4s}.pjax-loader-bar .progress{position:fixed;top:0;left:0;height:2px;background:#77b6ff;box-shadow:0 0 10px rgba(119,182,255,0.7);-webkit-transition:width 0.4s ease;transition:width 0.4s ease}.pjax-loader-bar.is-loading{opacity:1;-webkit-transition:none;transition:none}.page-profile .feed-icon{z-index:2}.profilecols .filter-bar{background-color:#fff}.profilecols .filter-bar .form-control{width:260px}.profilecols .filter-bar li{font-size:14px;list-style:none}.profilecols .filter-bar .filter-selected{font-weight:bold;color:#000}.vcard-names{line-height:1}.vcard-fullname{font-size:26px;line-height:30px}.vcard-username{font-size:20px;font-style:normal;font-weight:300;line-height:24px;color:#666}.vcard-details{list-style:none}.vcard-details .css-truncate.css-truncate-target{width:100%;max-width:100%}.vcard-details .css-truncate.css-truncate-target>div{overflow:hidden;text-overflow:ellipsis}.vcard-detail{padding-left:24px;font-size:14px}.vcard-detail .octicon{float:left;width:16px;margin-left:-24px;color:#999;text-align:center}.member-badge{padding-left:24px;font-size:14px;color:#4078c0}.member-badge .octicon{float:left;width:16px;margin-left:-24px;color:#999;text-align:center}.member-badge+.member-badge{padding-top:0;margin-top:-3px;border-top:0}.vcard-stats{text-align:center}.vcard-stats::before{display:table;content:""}.vcard-stats::after{display:table;clear:both;content:""}.vcard-stat{float:left;width:33.333%;font-size:11px;text-transform:capitalize}.vcard-stat-count{font-size:28px;font-weight:bold;line-height:1}.vcard-stat:hover{text-decoration:none}.vcard-stat:hover .text-muted{color:inherit}.new-user-avatar-cta{font-size:14px;color:#244f79;background-color:#f1f6fb;border:solid 1px #d0e5f8;border-radius:3px}.btn-block-user{color:inherit}.btn-block-user:hover{text-decoration:none}.user-profile-bio{margin-bottom:12px;overflow:hidden;font-size:14px;color:#767676}.form-group .form-control.user-profile-bio-field{width:440px;height:5.35em;min-height:0}.user-profile-bio-field-container,.user-profile-company-field-container{position:relative}.user-profile-bio-message{margin:5px 0 0;font-size:12px;color:#911}.user-profile-help-text{margin:5px 0 0;font-size:12px}.pull-request-tab-content{display:none}.pull-request-tab-content.is-visible{display:block}.discussion-timeline p.explain{margin:0;font-size:12px}.pull-request-ref-restore{display:none}.pull-request-ref-restore .animated-ellipsis-container{line-height:16px}.pull-request-ref-restore-text{display:block}.pull-discussion-timeline.is-pull-restorable .pull-request-ref-restore.last{display:block}.signed-out-comment{padding:15px;margin-top:15px;margin-left:64px;background-color:#fff9ea;border:solid 1px #dfd8c2;border-radius:3px}.signed-out-comment .btn{margin-right:3px;vertical-align:baseline}.inline-comment-form .signed-out-comment{padding:0;margin:5px;background-color:transparent;border:0}.stale-files-tab{margin-bottom:10px}.files-bucket{margin-bottom:15px}.pull-request-link{float:left;padding:2px 8px;margin-right:5px;font-size:13px;font-weight:bold;line-height:20px;border:1px solid rgba(65,131,196,0.5);border-radius:3px}.pull-request-link:hover{color:#fff;text-decoration:none;background:#4078c0;border-color:#4078c0}.split-diff .diffbar .container{padding-right:0;padding-left:0}.select-menu-divider{padding:8px 10px;margin-top:-1px;font-weight:bold;color:#333;background:#f4f4f4;border-top:1px solid #ddd;border-bottom:1px solid #ddd}.pr-toolbar{position:-webkit-sticky;position:sticky;top:0;z-index:30;height:60px;margin-top:-20px;background-color:#fff}.pr-toolbar::before{display:table;content:""}.pr-toolbar::after{display:table;clear:both;content:""}.pr-toolbar .stale-files-tab{float:left;padding:5px 10px;margin-top:-5px;margin-bottom:-5px;color:#c9510c;background-color:#fef2eb;border-radius:3px}.pr-toolbar .subset-files-tab{float:left;padding:5px 10px;margin-top:-5px;margin-bottom:-5px;color:#4078c0;background-color:#e6f1f6;border-radius:3px}.pr-toolbar .stale-files-tab-link{font-weight:bold;color:inherit}.pr-toolbar.is-stuck::after{position:fixed;top:0;right:0;left:0;z-index:-1;display:block;height:60px;content:"";border-bottom:1px solid rgba(0,0,0,0.1);box-shadow:0 1px 2px rgba(0,0,0,0.075)}.pr-toolbar .dropdown.active .dropdown-menu-content{display:block}.pr-toolbar .dropdown.active .dropdown-menu{top:150%}.pr-toolbar .dropdown.active .tooltipped::before,.pr-toolbar .dropdown.active .tooltipped::after{display:none}.pr-toolbar .right .diffbar-item{margin-right:0}.pr-toolbar .right .diffbar-item+.diffbar-item{margin-left:20px}.files-next-bucket .file,.files-next-bucket .full-commit{margin-top:0;margin-bottom:20px}.diff-options-content .selected{font-weight:bold}.diff-options-content .octicon{float:right;color:#6cc644}.toc-select .diffstat{margin-left:8px}.toc-select .filename{display:block;max-width:none}.toc-select .description{max-width:100%}.diffbar{height:20px;padding-top:20px;padding-bottom:20px;background-color:#fff}.diffbar::before{display:table;content:""}.diffbar::after{display:table;clear:both;content:""}.diffbar .container{width:auto}.diffbar .table-of-contents{margin-bottom:0}.diffbar .table-of-contents ol{margin-bottom:-15px}.diffbar .table-of-contents li{border-top:1px solid #eee}.diffbar .table-of-contents li:first-child{border-top:0}.diffbar .navigation-focus .text-emphasized{color:#fff}.diffbar-range-menu .select-menu-modal{width:380px}.diffbar-range-menu .css-truncate-target{max-width:280px}.diffbar-range-menu .select-menu-item:not(.select-menu-action){padding:8px 10px}.diffbar-range-menu .emoji{vertical-align:bottom}.diffbar-item{float:left;margin-right:20px;line-height:18px;vertical-align:middle}.sidebar-sign-off-requests .sign-off-requestee{line-height:20px}.finish-review-label,.add-comment-label,.review-cancel-button,.is-review-pending .start-review-label{display:none}.start-review-label,.is-review-pending .finish-review-label,.is-review-pending .add-comment-label{display:inline-block}.is-review-pending .review-cancel-button{display:block}.review-footer{max-width:780px;margin-right:auto;margin-left:auto}.review-footer .form-actions{margin-right:10px;margin-bottom:10px}.review-header-title{margin-bottom:0;font-weight:normal;line-height:1.1}.review-header-meta{padding-bottom:5px;margin-top:5px;font-size:14px;line-height:20px;color:#767676}.tutorial{position:absolute;bottom:42px;left:50%;z-index:100;width:200px;padding:12px;margin-left:-100px;text-align:center;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.15);border-radius:4px;box-shadow:0 3px 12px rgba(0,0,0,0.15)}.tutorial::before,.tutorial::after{position:absolute;display:inline-block;content:""}.tutorial::before{right:92px;bottom:-16px;left:auto;border:8px solid transparent;border-top-color:rgba(0,0,0,0.15)}.tutorial::after{right:93px;bottom:-14px;left:auto;border:7px solid transparent;border-top-color:#fff}.timeline-comment-label .tutorial{bottom:27px}.timeline-comment.is-pending{border-color:#dfd8c2}.timeline-comment.is-pending .timeline-comment-header{background-color:#fff9ea;border-bottom-color:#dfd8c2}.timeline-comment.is-pending .previewable-comment-form .comment-form-head.tabnav{background-color:#fff9ea;border-bottom-color:#dfd8c2}.timeline-comment.is-pending .tabnav-tab.selected{border-color:#dfd8c2}.timeline-comment.is-pending .timeline-comment-label{border-color:#dfd8c2}.timeline-comment-label.is-pending{float:none;margin-left:3px}.feature-callout{max-width:640px}.btn-lg{padding:10px 30px;font-size:15px}.pulse-blankslate{margin-top:20px}.diffstat-summary{padding:0 20px 0 0;font-size:16px;line-height:1.8;color:#767676;text-align:left;vertical-align:middle;border-radius:3px}.diffstat-summary a{color:#555}.diffstat-summary strong{color:#333}.pulse-graph{float:left;width:50%;padding:15px 15px 0;border-bottom:1px solid #eee}.pulse-graph:first-child{border-right:1px solid #eee}.authors-and-code .insertions{color:#6cc644}.authors-and-code .deletions{color:#bd2c00}.authors-and-code .section{display:table-cell;width:459px;height:150px}.pulse-authors-graph{position:relative;height:150px}.pulse-authors-graph>svg{width:100%}.pulse-authors-graph .dots{position:absolute;top:40px;right:0;left:0;width:64px;height:64px;margin:0 auto}.pulse-authors-graph .bar rect{fill:#c9510c;fill-opacity:0.7}.pulse-authors-graph .bar rect:hover{fill-opacity:1}.summary-stats{display:table;width:100%;table-layout:fixed}.summary-stats li{display:table-cell;color:#767676;text-align:center;border-left:1px solid #eee}.summary-stats li a{display:block;padding-bottom:10px;color:#767676;text-decoration:none}.summary-stats li a:hover{background:#fafafa}.summary-stats li .octicon-git-pull-request{color:#6e5494}.summary-stats li .octicon-git-branch{color:#6cc644}.summary-stats li .octicon-issue-closed{color:#bd2c00}.summary-stats li .octicon-issue-opened{color:#6cc644}.summary-stats li:first-child{border-left:0;border-bottom-left-radius:3px}.summary-stats li .num{display:block;padding-top:10px;font-size:16px;font-weight:bold;color:#000}.pulse-sections{margin-top:20px;clear:both}.pulse-section{padding:0;clear:both;font-size:14px;color:#666}.pulse-section p{margin-top:20px}.day-name{fill:#555}circle.day{fill:#444;stroke-width:0}circle.day:hover{fill:#4078c0}line.axis{stroke:#eee;stroke-width:1;shape-rendering:crispedges}line.axis.even{stroke:#e0e0e0}.quick-issue-modal{display:none}.quick-issue-modal-footer{margin-bottom:0}.quick-issue-thanks{display:none;font-size:18px}.quick-issue-link{margin-left:30px}.quick-issue-body{display:block;width:100%}.quick-issue-form{position:relative}.quick-issue-form .suggestions{margin-bottom:0;margin-left:0}.quick-issue-form .drag-and-drop{font-size:10px}.readme.contributing>div{max-height:250px;overflow:auto}.readme .markdown-body,.readme .plain{padding:45px;word-wrap:break-word;background-color:#fff;border:1px solid #ddd;border-bottom-right-radius:3px;border-bottom-left-radius:3px;}.readme .plain pre{font-size:15px;white-space:pre-wrap}.file .readme .markdown-body{padding:45px;border:0;border-radius:0}.file .readme table[data-table-type="yaml-metadata"]{font-size:12px;line-height:1}.file .readme table[data-table-type="yaml-metadata"] table{margin:0}.releases-tag-list{width:100%;margin-bottom:20px;border-top:1px solid #eee}.releases-tag-list tr{border-bottom:1px solid #eee}.releases-tag-list td{padding:12px 0;vertical-align:top}.releases-tag-list td.date{padding-right:10px;white-space:nowrap}.releases-tag-list td.date a{color:#767676}.releases-tag-list td.main{padding-right:10px}.releases-tag-list td.ancillary{text-align:right;white-space:nowrap}.releases-tag-list h4{margin:0;font-size:14px}.releases-tag-list p{margin:0;font-size:13px;color:#767676}.releases-tag-list p a{font-weight:bold;color:#666}.tag-info h3{margin-top:0;margin-bottom:0;font-size:14px;line-height:20px}.tag-info h3 a{color:#666}.tag-info h3 a .tag-name{color:#000}.tag-references{margin:0;font-size:13px;list-style-type:none}.tag-references>li{display:inline-block;margin-right:10px}.tag-references>li.commit{font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px;line-height:20px}.tag-references>li a{color:#767676;text-decoration:none}.tag-references>li a:hover{color:#4078c0}.release-downloads-header{margin-top:30px}.release-downloads{margin-top:10px;font-size:14px;border-top:1px solid #eee}.release-downloads li{display:block;padding-top:8px;padding-bottom:8px;border-bottom:1px solid #eee}.release-downloads strong{padding-left:5px}.release-downloads .octicon{float:left}.release-downloads .octicon-package{margin-left:-3px}.release-timeline{position:relative;border-top:1px solid #eee}.release-timeline-tags{list-style-type:none}.release-timeline-tags>li{display:block}.release-timeline-tags>li::before{display:table;content:""}.release-timeline-tags>li::after{display:table;clear:both;content:""}.release-timeline-tags .date,.release-timeline-tags .main{position:relative;float:left;padding:20px}.release-timeline-tags .main{width:80%;border-left:2px solid #eee}.release-timeline-tags .date{width:20%;padding-left:0;line-height:40px;color:#767676;text-align:right}.release-timeline-tags .date::after{position:absolute;top:50%;right:-7px;z-index:10;display:block;width:12px;height:12px;box-sizing:border-box;margin-top:-6px;content:" ";background-color:#eee;border:2px solid #fff;border-radius:6px}.release-timeline-tags .octicon-tag{padding-left:5px;color:#ccc}.release-timeline-tags .expander{position:relative;display:none}.release-timeline-tags .expander .date{padding-right:35px;line-height:20px}.release-timeline-tags .expander .date::after{display:none}.release-timeline-tags .expander .main{padding-left:35px;line-height:20px}.release-timeline-tags.is-collapsed .expander{display:block}.release-timeline-tags.is-collapsed>.collapsable{display:none}.release-timeline-tags .expander-dots{position:absolute;top:18px;left:-22px;z-index:10;width:44px;text-align:center;cursor:pointer;background-color:#eee;border:2px solid #fff;border-radius:4px}.release-timeline-tags .expander-dots .expander-dot{display:inline-block;width:4px;height:4px;margin-top:-2px;vertical-align:middle;background-color:#767676;border-radius:2px}.release-timeline-tags .expander-text{font-weight:bold;color:#666;cursor:pointer}.release-timeline-tags .expander-text:hover{color:#4078c0}.release-timeline-tags .expander-text:hover .expander-dots{background-color:#4078c0}.release-timeline-tags .expander-text:hover .expander-dots .expander-dot{background-color:#fff}.release::before{display:table;content:""}.release::after{display:table;clear:both;content:""}.release .tag-references{margin-top:8px}.release .tag-references>li{display:block;margin:0 0 5px}.release-meta{float:left;width:20%;padding:40px 20px;text-align:right;vertical-align:top}.release-body{float:left;width:80%;padding:40px 20px;border-left:2px solid #eee}.release-title{margin:0 60px 0 0}.release-edit{float:right}.release-authorship{margin-top:5px;margin-bottom:20px;font-size:14px;color:#767676}.release-authorship a{font-weight:bold;color:#666}.release-label{display:inline-block;padding:5px 10px;margin-top:1px;margin-bottom:10px;font-size:14px;font-weight:bold;color:#fff;background-color:#000;border-radius:3px}.release-label.latest{background-color:#6cc644}.release-label.draft{background-color:#bd2c00}.release-label.prerelease{background-color:#c9510c}.release-label a{color:#fff}.new-release .sidebar h3{margin:40px 0 -10px;font-size:14px}.new-release .sidebar h3:first-child{margin-top:15px}.new-release .default,.new-release .saved,.new-release .saving,.new-release .error{display:none}.new-release .error{color:#bd2c00}.new-release .is-default .default,.new-release .is-saving .saving,.new-release .is-saved .saved{display:inline-block}.new-release .saving img{vertical-align:top}.drop-target .octicon{color:#e5e5e5;vertical-align:middle}.drop-target p{height:65px;padding:16px 0;font-size:14px;text-align:center;border-color:#ddd;border-style:dashed}.drop-target .octospinner{vertical-align:middle}.uploaded-files{background:#fff;border-top-left-radius:3px;border-top-right-radius:3px}.uploaded-files.not-populated+.drop-target p{border-top:dashed 1px #ccc;border-top-left-radius:3px;border-top-right-radius:3px}.uploaded-files.is-populated{border:1px solid #ddd;border-bottom-color:#e5e5e5}.uploaded-files.is-populated+.drop-target p{border-top:0;border-top-left-radius:0;border-top-right-radius:0}.uploaded-files>li{padding:8px 10px;margin:0;line-height:22px;list-style-type:none;border-top:1px solid #eee}.uploaded-files>li.template{display:none}.uploaded-files>li .delete-pending{display:none}.uploaded-files>li.delete{color:#767676;background:#f9f9f9}.uploaded-files>li.delete:nth-child(2){border-top-left-radius:3px;border-top-right-radius:3px}.uploaded-files>li.delete .delete-pending{display:block}.uploaded-files>li.delete .live{display:none}.uploaded-files>li.delete .filename{color:#bd2c00}.uploaded-files>li:nth-child(2){border-top:0}.uploaded-files .filename{font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:11px}.uploaded-files .filesize{font-size:12px;color:#767676}.uploaded-files .form-control{width:490px;padding:2px 4px;margin-right:6px;border-radius:2px}.uploaded-files .remove{float:right;margin-top:2px;color:#767676}.uploaded-files .remove:hover{color:#bd2c00}.uploaded-files .undo{float:right}.upload-progress{position:relative;height:3px;margin-top:3px;background:#fff;border:0;border-radius:30px;box-shadow:0 1px 1px #fff,inset 0 1px 1px rgba(255,255,255,0.5)}.upload-progress .upload-meter{position:absolute;top:0;height:100%;background-image:-webkit-linear-gradient(#8dd2f7, #58b8f4);background-image:linear-gradient(#8dd2f7, #58b8f4);border-radius:30px}.release-body-form .previewable-comment-form .comment-form-head.tabnav{padding:0;background-color:transparent}.release-body-form .previewable-comment-form .write-content,.release-body-form .previewable-comment-form .preview-content{padding:0 0 10px;margin:0}.release-tag-form .for-loading,.release-tag-form .for-empty,.release-tag-form .for-valid,.release-tag-form .for-invalid,.release-tag-form .for-duplicate,.release-tag-form .for-pending{display:none}.release-tag-form.is-loading .for-loading{display:block}.release-tag-form.is-empty .for-empty{display:block}.release-tag-form.is-valid .for-valid{display:block}.release-tag-form.is-invalid .for-invalid{display:block}.release-tag-form.is-duplicate .for-duplicate{display:block}.release-tag-form.is-pending .for-pending{display:block}.release-target-wrapper{display:inline-block}.releases-target-menu{display:inline-block;margin-left:5px}.releases-target-menu .btn-sm{line-height:32px}.releases-target-menu .select-menu-button::before{top:14px}.release-show{border-top:1px solid #eee}.release-show .release-edit{display:none}.repo-file-upload-progress{position:relative;height:0;overflow:hidden;color:#246;background:#f7fbfe;border-bottom-right-radius:3px;border-bottom-left-radius:3px;box-shadow:0 0 0 1px #d0dbe7 inset;-webkit-transition:height 0.2s ease-out;transition:height 0.2s ease-out}.repo-file-upload-progress.active{height:50px}.repo-file-upload-progress.is-file-list{border-bottom-right-radius:0;border-bottom-left-radius:0}.repo-file-upload-progress .repo-file-upload-meter{position:absolute;top:1px;left:1px;width:0;height:48px;background:#d8e8f7}.repo-file-upload-progress .repo-file-upload-meter-text{position:absolute;top:7px;left:10px}.repo-file-upload-progress .repo-file-upload-meter-text .repo-file-upload-info{display:block;font-weight:bold}.repo-file-upload-progress .repo-file-upload-meter-text .repo-file-upload-meter-filename{display:block;margin:0}.manifest-commit-form{margin-top:20px}.repo-file-upload-outline{width:100%;height:100%}.repo-file-upload-target{position:relative;padding-top:100px;padding-bottom:80px;color:#666}.repo-file-upload-target.is-progress-bar,.repo-file-upload-target.is-uploading{border-bottom:0;border-bottom-right-radius:0;border-bottom-left-radius:0}.repo-file-upload-target.is-file-list{border-bottom-right-radius:0;border-bottom-left-radius:0}.repo-file-upload-target.is-uploading .repo-file-upload-text.initial-text,.repo-file-upload-target.is-failed .repo-file-upload-text.initial-text,.repo-file-upload-target.is-default .repo-file-upload-text.initial-text{display:none}.repo-file-upload-target.is-uploading .repo-file-upload-text.alternate-text,.repo-file-upload-target.is-failed .repo-file-upload-text.alternate-text,.repo-file-upload-target.is-default .repo-file-upload-text.alternate-text{display:block}.repo-file-upload-target.is-uploading.dragover .repo-file-upload-text,.repo-file-upload-target.is-failed.dragover .repo-file-upload-text,.repo-file-upload-target.is-default.dragover .repo-file-upload-text{display:none}.repo-file-upload-target .repo-file-upload-text.initial-text{display:block}.repo-file-upload-target .repo-file-upload-text.alternate-text{display:none}.repo-file-upload-target .repo-file-upload-text,.repo-file-upload-target .repo-file-upload-drop-text{margin-bottom:5px}.repo-file-upload-target .repo-file-upload-choose{display:inline-block;margin-top:0;font-size:18px}.repo-file-upload-target .manual-file-chooser{margin-left:0}.repo-file-upload-target .repo-file-upload-outline{position:absolute;top:3%;left:1%;width:98%;height:94%}.repo-file-upload-target.is-failed .repo-file-upload-outline,.repo-file-upload-target.is-bad-file .repo-file-upload-outline,.repo-file-upload-target.is-too-big .repo-file-upload-outline,.repo-file-upload-target.is-too-many .repo-file-upload-outline,.repo-file-upload-target.is-empty .repo-file-upload-outline{height:85%}.repo-file-upload-target.dragover .repo-file-upload-text{display:none}.repo-file-upload-target.dragover .repo-file-upload-choose{visibility:hidden}.repo-file-upload-target.dragover .repo-file-upload-drop-text{display:block}.repo-file-upload-target.dragover .repo-file-upload-outline{border:6px #ddd dashed;border-radius:5px}.repo-file-upload-target .repo-file-upload-drop-text{display:none}.repo-file-upload-errors{display:none}.repo-file-upload-errors .error{display:none}.is-failed .repo-file-upload-errors,.is-bad-file .repo-file-upload-errors,.is-too-big .repo-file-upload-errors,.is-too-many .repo-file-upload-errors,.is-hidden-file .repo-file-upload-errors,.is-empty .repo-file-upload-errors{position:absolute;right:0;bottom:0;left:0;display:block;padding:5px 8px;line-height:1.5;text-align:left;background-color:#fff;border-top:1px solid #e5e5e5;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.is-file-list .repo-file-upload-errors{border-bottom-right-radius:0;border-bottom-left-radius:0}.is-failed .repo-file-upload-errors .failed-request,.is-bad-file .repo-file-upload-errors .failed-request{display:inline-block}.is-too-big .repo-file-upload-errors .too-big{display:inline-block}.is-hidden-file .repo-file-upload-errors .hidden-file{display:inline-block}.is-too-many .repo-file-upload-errors .too-many{display:inline-block}.is-empty .repo-file-upload-errors .empty{display:inline-block}.repo-file-upload-tree-target{position:fixed;top:0;left:0;z-index:1000;width:100%;height:100%;padding:12px;color:#666;visibility:hidden;background:-webkit-radial-gradient(center, ellipse, #fff 0%, rgba(255,255,255,0.85) 65%, rgba(255,255,255,0.85) 100%);background:radial-gradient(ellipse at center, #fff 0%, rgba(255,255,255,0.85) 65%, rgba(255,255,255,0.85) 100%);opacity:0;-webkit-transition:visibility 0.2s, opacity 0.2s;transition:visibility 0.2s, opacity 0.2s}.repo-file-upload-tree-target .repo-file-upload-outline{border:6px #ddd dashed;border-radius:5px}.dragover .repo-file-upload-tree-target{visibility:visible;opacity:1}.dragover .repo-file-upload-tree-target .repo-file-upload-slate{top:50%;opacity:1}.dragover .repo-file-upload-tree-target .files-lg>.file-graph{opacity:1;-webkit-transform:translateX(4px);transform:translateX(4px)}.dragover .repo-file-upload-tree-target .files-lg>.file-zip{opacity:1;-webkit-transform:translateX(9px);transform:translateX(9px)}.dragover .repo-file-upload-tree-target .files-lg>.file-generic{opacity:1;-webkit-transform:translateX(-4px);transform:translateX(-4px)}.dragover .repo-file-upload-tree-target .files-lg>.file-acrobat{opacity:1;-webkit-transform:translateX(-9px);transform:translateX(-9px)}.repo-file-upload-slate{position:absolute;top:50%;width:100%;text-align:center;-webkit-transform:translateY(-50%);transform:translateY(-50%)}.repo-file-upload-slate h2{margin-top:5px}.repo-file-upload-slate .file-graph{opacity:0;-webkit-transition:opacity 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275),-webkit-transform 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275);transition:opacity 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275),transform 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275);-webkit-transform:translateX(50px);transform:translateX(50px)}.repo-file-upload-slate .file-zip{opacity:0;-webkit-transition:opacity 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275),-webkit-transform 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275);transition:opacity 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275),transform 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275);-webkit-transform:translateX(90px);transform:translateX(90px)}.repo-file-upload-slate .file-generic{opacity:0;-webkit-transition:opacity 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275),-webkit-transform 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275);transition:opacity 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275),transform 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275);-webkit-transform:translateX(-50px);transform:translateX(-50px)}.repo-file-upload-slate .file-acrobat{opacity:0;-webkit-transition:opacity 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275),-webkit-transform 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275);transition:opacity 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275),transform 0.2s 0.12s cubic-bezier(0.175, 0.885, 0.32, 1.275);-webkit-transform:translateX(-90px);transform:translateX(-90px)}.repo-file-upload-file-wrap .name{color:#888}.repo-file-upload-file-wrap .actions{width:50px;padding-right:10px;text-align:right}.repo-file-upload-file-wrap .remove-file{color:#888}.repo-file-upload-file-wrap .remove-file:hover{color:#666}.repo-upload-breadcrumb{margin-bottom:18px}.mini-repo-list{list-style:none}.mini-repo-list>:first-child .mini-repo-list-item{border-top:0}.mini-repo-list>:last-child .mini-repo-list-item{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.mini-repo-list .no-repo{padding:15px;color:#767676;text-align:center}.mini-repo-list .repo-name{font-weight:bold}.mini-repo-list-item{position:relative;display:block;padding:6px 64px 6px 30px;font-size:14px;border-top:1px solid #e5e5e5}.mini-repo-list-item:hover{text-decoration:none}.mini-repo-list-item:hover .repo,.mini-repo-list-item:hover .owner{text-decoration:underline}.mini-repo-list-item .repo-icon{float:left;margin-top:2px;margin-left:-20px;color:#666}.mini-repo-list-item .repo-and-owner{max-width:220px}.mini-repo-list-item .owner{max-width:110px}.mini-repo-list-item .repo{font-weight:bold}.mini-repo-list-item .stars{position:absolute;top:0;right:10px;margin-top:6px;font-size:12px;color:#888}.mini-repo-list-item .repo-description{display:block;max-width:100%;font-size:12px;line-height:21px;color:#767676}.popular-repos .mini-repo-list-item .stars{margin-top:16px}.popular-repos .mini-repo-list-item .stars .octicon{vertical-align:text-bottom}.popular-repos .no-description .mini-repo-list-item{padding-top:17px;padding-bottom:16px}.private .mini-repo-list-item{background-color:#fff9ea}.private .mini-repo-list-item .repo-icon{color:#4c4a42}.filter-bar{padding:10px;background-color:#fafafa;border-bottom:1px solid #e5e5e5}.filter-bar::before{display:table;content:""}.filter-bar::after{display:table;clear:both;content:""}.user-repos .filter-bar{text-align:center}.filter-repos{padding-bottom:0}.repo-filterer{display:inline-block;margin-top:6px;list-style:none}.repo-filterer li{display:inline-block}.repo-filterer .repo-filter{display:inline-block;padding:5px 5px 6px;margin-right:5px;border-bottom:2px solid transparent}.repo-filterer .repo-filter:hover{text-decoration:none;border-bottom-color:#e5e5e5}.repo-filterer .repo-filter.filter-selected{color:#333;text-decoration:none;border-bottom-color:#d26911;outline:none}.more-repos{text-align:center;box-shadow:inset 0 1px 0 #e5e5e5}.more-repos img{margin:11px auto}.more-repos-link{display:block;padding:10px;color:#7aa1d3}.more-repos-link:hover{color:#4078c0;text-decoration:none}.more-repos-link.is-loading{text-indent:-9999px;cursor:default;background-image:url("/images/spinners/octocat-spinner-16px.gif");background-repeat:no-repeat;background-position:center center}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){.more-repos-link.is-loading{background-image:url("/images/spinners/octocat-spinner-32.gif");background-size:16px 16px}}.empty-repo{font-size:14px}.empty-repo .url-box{display:block;width:100%;height:auto;padding:0;margin:0;border:0}.empty-repo .clone-urls{width:100%}.empty-repo .or-text{margin-right:5px;margin-left:5px}.empty-repo-setup-option .copyable-terminal-content{font-size:14px}.empty-repo-setup-option h3{margin-top:0}.empty-repo-setup-option p:last-child{margin-bottom:0}.give-access-setup-option{margin-bottom:20px}.fork-missing-targets-header{padding-top:15px;margin-bottom:15px;font-size:20px;font-weight:normal;border-top:1px solid #eee}.facebox .fork-existing-targets-list,.facebox .fork-disallowed-list{margin-left:0;list-style:none}.facebox .fork-existing-targets-list .user-mention,.facebox .fork-disallowed-list .user-mention{display:block}.fork-disallowed-list li+li{margin-top:5px}.url-box{width:100%;height:26px;padding:10px 10px 0;margin-top:10px;margin-left:-10px;border-top:1px solid #ddd}.url-box p{float:left;height:26px;margin:0 0 0 5px;line-height:26px}.url-box p strong{color:#000}.clone-urls{display:table;float:left;width:585px}.clone-url-button{display:table-cell;width:1%;vertical-align:top}.clone-url-button:first-child .clone-url-link{border-top-left-radius:3px;border-bottom-left-radius:3px}.clone-url-button>.clone-url-link{position:relative;display:block;padding:0 9px;margin-right:-1px;font-size:11px;font-weight:bold;line-height:26px;color:#333;text-decoration:none;text-shadow:0 1px 0 #fff;white-space:nowrap;cursor:pointer;background-image:-webkit-linear-gradient(#fafafa, #eaeaea);background-image:linear-gradient(#fafafa, #eaeaea);border:1px solid #ccc}.clone-url-button>.clone-url-link:hover,.clone-url-button>.clone-url-link:active{z-index:3;color:#fff;text-decoration:none;text-shadow:0 -1px 0 rgba(0,0,0,0.25);background-image:-webkit-linear-gradient(#599bcd, #3072b3);background-image:linear-gradient(#599bcd, #3072b3);border-color:#2a65a0}.clone-url-button>.clone-url-link:active{background-color:#3072b3;background-image:none;border-color:#25588c;box-shadow:inset 0 3px 5px rgba(0,0,0,0.15)}.clone-url-button>.clone-url-link:focus{outline:0}.clone-url-button+.clone-url-button>.clone-url-link{box-shadow:inset 1px 0 0 #fff}.clone-url-button+.clone-url-button>.clone-url-link:hover{box-shadow:none}.clone-url-button+.clone-url-button>.clone-url-link:active{box-shadow:inset 0 3px 5px rgba(0,0,0,0.15)}.clone-url-button.selected>.clone-url-link,.clone-url-button.selected>.clone-url-link:hover{z-index:2;color:#333;text-shadow:0 1px 0 rgba(255,255,255,0.6);background-color:#ccc;background-image:-webkit-linear-gradient(#ccc, #d5d5d5);background-image:linear-gradient(#ccc, #d5d5d5);border-color:#bbb;box-shadow:inset 0 2px 3px rgba(0,0,0,0.075)}.url-field{position:relative;width:100%;min-height:26px;padding:0 5px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px;border-radius:0}.url-field:focus{z-index:2}.url-box-clippy .zeroclipboard-button{margin-left:0 !important;border-left:0;border-radius:0 3px 3px 0}.pagehead.repohead .select-menu .select-menu-modal-holder{z-index:25}.repository-import .repository-import-label{display:block;margin-bottom:12px;font-size:18px;font-weight:normal}.repository-import .state-header{font-size:16px}.text-yellow{color:#e9dba5}.timeout{width:auto;height:300px;padding:0;margin:20px 0;background-color:transparent;border:0}.timeout h3{padding-top:100px;color:#767676}.overall-summary{position:relative;margin-bottom:10px;border:1px solid #ddd;border-radius:3px}.overall-summary-bottomless{margin-bottom:0;border-bottom:0;border-radius:3px 3px 0 0}.numbers-summary{display:table;width:100%;table-layout:fixed}.numbers-summary li{display:table-cell;padding:0;text-align:center;white-space:nowrap}.numbers-summary a,.numbers-summary .nolink{display:block;padding:10px 0;color:#767676;text-decoration:none}.numbers-summary .octicon{color:#999}.numbers-summary a:hover{color:#4078c0}.numbers-summary a:hover .num{color:inherit}.repo-private-label,.gist-secret-label{display:inline-block;padding:4px 5px 3px;font-size:11px;font-weight:300;line-height:11px;color:#4c4a42;text-transform:uppercase;vertical-align:middle;background-color:#ffefc6;border-radius:3px}.repository-meta{margin:0 0 20px;clear:both;font-size:16px;color:#666}.repository-meta::before{display:table;content:""}.repository-meta::after{display:table;clear:both;content:""}.repository-meta .repo-description-field{width:540px}.repository-meta .repo-website-field{width:300px}.repository-meta .edit-repository-meta{display:none;margin-bottom:5px;font-size:13px}.repository-meta .edit-repository-meta .field{display:inline-block;margin-right:5px}.repository-meta .edit-repository-meta label{display:block;margin-bottom:6px;font-weight:bold;color:#333}.repository-meta.open .repository-meta-content,.repository-meta.open .edit-link{display:none}.repository-meta.open .edit-repository-meta{display:block}.experiment-repo-nav .capped-cards .capped-card{width:480px}.experiment-repo-nav .new-issue-form{padding-top:0;border-top:0}.experiment-repo-nav.repohead .repo-mirror{padding-left:20px}.iconbutton .octicon{margin-right:0}.file-navigation::before{display:table;content:""}.file-navigation::after{display:table;clear:both;content:""}.file-navigation.in-mid-page{margin-top:15px}.file-navigation .select-menu-button .css-truncate-target{max-width:200px}.file-navigation .branch-select-menu{margin-right:6px;margin-bottom:10px}.file-navigation .new-pull-request-btn{float:left}.file-navigation .breadcrumb{float:left;margin-top:0;margin-left:5px;font-size:16px;line-height:26px}.file-navigation+.breadcrumb{margin-bottom:10px}.file-navigation .btn-group{margin-bottom:10px;margin-left:10px}.file-navigation .get-repo-select-menu{margin-left:6px}.file-navigation .get-repo-select-menu.active .btn-primary{text-shadow:0 1px 0 rgba(0,0,0,0.15);background-color:#569e3d;background-image:none;border-color:#418737}.file-navigation .get-repo-modal{top:6px;width:352px}.file-navigation .get-repo-modal .https-clone-options{display:block}.file-navigation .get-repo-modal .ssh-clone-options{display:none}.file-navigation .get-repo-modal.on .https-clone-options{display:none}.file-navigation .get-repo-modal.on .ssh-clone-options{display:block}.file-navigation .btn-change-protocol{font-size:12px}.file-navigation .get-repo-decription-text{font-size:13px}.file-navigation .get-repo-btn{float:left;width:50%;padding:10px 0;text-align:center;border:0;border-top:1px solid #e9e9e9;border-radius:0}.file-navigation .get-repo-btn:first-child{border-right:1px solid #e9e9e9;border-bottom-left-radius:3px}.file-navigation .get-repo-btn:nth-child(1):nth-last-child(3){border-radius:0}.file-navigation .get-repo-btn:last-child{border-bottom-right-radius:3px}.file-navigation .get-repo-btn.btn-block,.file-navigation .get-repo-btn:only-child{width:100%;border-right:0;border-radius:0 0 3px 3px}.file-navigation .clone-options{padding:6px 10px 10px}.file-navigation .input-group.has-zeroclipboard-disabled{width:100%}.file-navigation .input-group.has-zeroclipboard-disabled .form-control{border-radius:3px}.file-navigation .input-group.has-zeroclipboard-disabled .input-group-button{display:none}.file-wrap{margin-bottom:10px;border:1px solid #ddd;border-top:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.file-wrap .include-fragment-error{display:none}.file-wrap.is-error .include-fragment-error{display:table-row}table.files{width:100%;background:#fff;border-radius:2px}table.files tr.navigation-focus td{background:#f5f5f5}table.files td{padding:6px 3px;line-height:20px;border-top:1px solid #eee}table.files td.icon{width:17px;padding-right:2px;padding-left:10px;color:#767676}table.files td.icon .octicon-file-directory{color:#80a6cd}table.files td.icon .spinner{position:relative;top:3px;display:none;margin-top:-3px;margin-left:-2px}table.files td .simplified-path{color:#888}table.files td .css-truncate{max-width:100%}table.files td.content{max-width:180px}table.files td.message{max-width:442px;padding-left:10px;overflow:hidden;color:#888}table.files td.message .emoji{vertical-align:top}table.files td.message a{color:#888}table.files td.message a:hover{color:#4078c0}table.files td.age{width:125px;padding-right:10px;color:#888;text-align:right;white-space:nowrap}table.files .message .tooltipped::before,table.files .message .tooltipped::after{display:none}table.files tr.is-loading td.icon .octicon{display:none}table.files tr.is-loading td.icon .spinner{display:inline-block}table.files tr.up-tree{border-top:1px solid #eee;border-bottom:1px solid #eee}table.files tr.up-tree a{padding:3px 6px;margin-left:-3px;font-weight:bold;border-radius:2px}table.files tr.up-tree a:hover{background-color:#ddd}table.files tbody tr:first-child td{border-top:0}.branch-infobar{padding:8px;color:#767676;background-color:#fafafa;border:solid #ddd;border-width:1px 1px 0;border-top-left-radius:3px;border-top-right-radius:3px}.branch-infobar::before{display:table;content:""}.branch-infobar::after{display:table;clear:both;content:""}.branch-infobar .muted-link{display:inline-block;margin-left:10px}.branch-infobar .muted-link .octicon{color:#bbb}.branch-infobar .muted-link:hover .octicon{color:inherit}.fork-select-fragment{text-align:center}.spinner-forking{display:block;margin:20px auto 40px}.prereceive-feedback{padding:15px;margin-bottom:15px;border:1px solid #ddd;border-left:3px solid #cea61b;border-radius:3px}.prereceive-feedback-heading{margin-top:0;margin-bottom:10px;color:#cea61b}.file-navigation-options{float:right;margin-left:3px}.file-navigation-options.active .dropdown-menu-content{display:block}.file-navigation-options .dropdown-menu{width:360px;padding:15px}.file-navigation-options .dropdown-divider{margin:15px -15px}.file-navigation-options .clone-url{display:none}.file-navigation-options .clone-url.open{display:block}.file-navigation-options .clone-url h3{margin-bottom:5px;font-size:14px}.file-navigation-option{position:relative;display:inline-block;margin-left:3px}.file-navigation-option .select-menu{display:inline-block;margin-right:0;margin-bottom:0;vertical-align:middle}.file-navigation-option .select-menu-button .octicon:only-child{margin-left:2px}.file-navigation-option .zeroclipboard-button{padding-right:8px}.file-navigation-option .input-group{width:290px}.file-navigation-option .input-group .form-control{width:calc(100% + 2px);height:26px;min-height:0;margin-right:-1px;margin-left:-1px;border-radius:0}.file-navigation-option .input-group .select-menu-button{position:relative;z-index:2}.file-navigation-option .input-group.has-zeroclipboard-disabled .form-control{width:calc(100% + 1px);border-top-right-radius:3px;border-bottom-right-radius:3px}.file-navigation-option .input-group.has-zeroclipboard-disabled .input-group-button:last-child{display:none}.profile-picture{margin:10px 0 0}.profile-picture p{float:left;margin:0;line-height:30px}.profile-picture img{float:left;margin:0 10px 0 0;border-radius:3px}.app-owner{margin:15px 0 0}.edit-profile-avatar .drag-and-drop{padding:0;color:#666;border-width:0}.edit-profile-avatar input{cursor:pointer}.edit-profile-avatar.is-bad-file{border:0}.edit-profile-avatar .manual-file-chooser{position:absolute;top:0;left:0;width:120px;height:34px;padding:0;margin-left:-44px;cursor:pointer}.button-change-avatar{overflow:hidden}.croppable-avatar{display:none}.profile-picture-cropper{max-width:400px;margin:0 auto 15px;text-align:center}.profile-picture-cropper>img{max-width:100%}.profile-picture-cropper .jcrop-holder{display:inline-block}.profile-picture-spinner{display:inline-block;background-image:url("/images/spinners/octocat-spinner-128.gif");background-repeat:no-repeat;background-position:center;background-size:64px 64px}.avatar-upload{float:left;width:340px;margin-left:20px}.avatar-upload .flash{width:100%;padding:30px 15px;border:dashed 1px #bd2c00;box-shadow:none}.avatar-upload .upload-state{display:none;padding:10px 0}.avatar-upload .upload-state p{margin:0;font-size:12px;color:#767676}.avatar-upload .avatar-upload .octicon{display:inline-block}.is-default .avatar-upload .default{display:block}.is-uploading .avatar-upload .loading{display:block;padding:0}.is-uploading .avatar-upload .loading img{vertical-align:top}.is-uploading .avatar-upload .button-change-avatar{display:none}.is-bad-file .avatar-upload .bad-file{display:block;margin:0}.is-too-big .avatar-upload .too-big{display:block;margin:0}.is-bad-dimensions .avatar-upload .bad-dimensions{display:block;margin:0}.is-failed .avatar-upload .failed-request{display:block;margin:0}.is-empty .avatar-upload .file-empty{display:block;margin:0}dl.new-email-form{padding:10px 10px 0;margin:0 -10px 10px;border-top:1px solid #e5e5e5}span.label.default{padding:4px 6px;margin-left:4px;color:#fff;background-color:#6cc644;border-radius:3px}span.label.visibility{padding:4px 6px;margin-left:4px;color:#fff;background-color:#999;border-radius:3px}span.label.bouncing{padding:4px 6px;margin-left:4px;color:#fff;background-color:#daa520;border-radius:3px}.email-actions{float:right}.email-actions>span{float:left}.email-actions form{display:inline}.email-actions span.label{padding:0 10px;font-size:13px;color:#767676}.email-actions .octicon-alert{color:#ca5633}.boxed-group .fork-flag{margin-left:16px;font-size:12px;color:#767676}.user-keys-container .user-keys-message{display:none}.user-keys-container.has-keys .user-keys-message{display:block}.user-keys-container.has-keys .no-user-keys-message{display:none}.selected-user-key{background-color:#fff9ea}.user-key-type{padding-right:20px;padding-left:10px;text-align:center}.user-key-badge{display:block;padding-right:5px;padding-left:5px;margin-top:3px;font-size:12px;line-height:1.4;border:solid 1px #ddd;border-radius:3px}.user-key-email-badge{display:inline-table;margin-right:4px}.user-key-email{display:table-cell;padding:1px 5px;font-size:12px;line-height:1.4;border:solid 1px #ddd;border-radius:3px}.user-key-email.unverified{border-top-right-radius:0;border-bottom-right-radius:0}.user-key-email-unverified{display:table-cell;padding-right:5px;padding-left:5px;font-size:11px;color:#666;background-color:#ecebec;border:solid 1px #ddd;border-left:0;border-top-right-radius:3px;border-bottom-right-radius:3px}.user-key-details{width:400px;line-height:1.6;white-space:normal}.user-key-details code{font-size:13px}.user-key-actions{width:150px}.recent-user-key{color:#6cc644}.recent-user-key-access{color:#55a532}.unverified-user-key,.unverified-user-key-notice{color:#bd2c00}.notification-center .overview{padding:0 10px 10px;border-bottom:1px solid #ddd}.oauth-app-info-container .float-left-container{float:left;text-align:left}.oauth-app-info-container .float-right-container{float:right;text-align:right}.oauth-app-info-container dl.keys{margin:10px 0}.oauth-app-info-container dl.keys dt{margin-top:10px;font-weight:bold;color:#767676}.oauth-app-info-container dl.keys dd{font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;color:#333}.oauth-app-info-container .user-count{font-size:30px;font-weight:300;color:#767676}.logo-uploader-container{display:block}.logo-upload{position:relative;display:inline-block}.logo-upload a.delete,.logo-upload span.delete{position:absolute;left:88px;display:none;padding:8px 10px}.logo-upload a.delete:hover,.logo-upload span.delete:hover{color:#bd2c00}.logo-upload-container{display:inline-block}.logo-upload-container .logo-upload-label .manual-file-chooser{top:0;left:0;width:130px;height:34px;padding:0;margin-left:0;cursor:pointer}.logo-upload-container .upload-state{padding:10px 0}.logo-upload-container .upload-state p{margin:0;font-size:12px;color:#767676}.logo-box{display:inline-block;float:left;width:120px;height:120px;background-color:#eee;border:1px solid #ccc;border-radius:3px}.logo-box img{display:none;width:118px;height:118px;border-radius:3px}.logo-placeholder{color:#767676;text-align:center;text-shadow:0 1px 0 #fff}.logo-placeholder p{margin:0;font-size:14px}.has-uploaded-logo .logo-placeholder,.has-uploaded-logo .or{display:none}.has-uploaded-logo:hover a.delete,.has-uploaded-logo:hover span.delete{display:block}.has-uploaded-logo .logo-box img{display:block}.saved-reply-form{border:1px solid #ddd;border-radius:3px}.saved-reply-form .tabnav-tab.selected{border-radius:3px 3px 0 0}.saved-reply-form .form-actions{margin-right:10px;margin-bottom:8px}.saved-reply-form .comment{border:0}.saved-reply-form .comment-body{padding:5px 5px 15px;background-color:transparent;border-bottom:1px solid #eee}.saved-reply-settings-container.has-replies .listgroup{display:block}.saved-reply-settings-container.has-replies .blankslate{display:none}.saved-reply-settings-container .listgroup{display:none}.access-token{border-bottom:1px solid #e5e5e5}.access-token:last-child{border:0}.access-token .last-used{margin-right:10px}.access-token .zeroclipboard-link{display:inline-block;vertical-align:text-bottom}.access-token.new-token{background-color:rgba(108,198,68,0.1)}.access-token.new-token .octicon-check{color:#6cc644}.access-token .token-description{max-width:450px;color:#333}.access-token .token{font-size:14px}.regenerate-token-cta .btn-danger{margin-left:30px}.personal-access-tokens-group{padding:0 10px}.personal-access-tokens label{display:inline}.personal-access-tokens label p{display:inline-block;margin:0;font-size:13px;font-weight:normal}.personal-access-tokens .child-scopes{margin-left:20px;list-style:none}.personal-access-tokens .child-scopes .token-scope{width:180px;font-weight:normal}.personal-access-tokens .child-scopes .child-scopes{margin-left:0}.token-scope{display:inline-block;width:200px;padding:2px 0;margin:0;font-size:13px;color:#333}.token-scope input{margin-right:5px}.callback-urls dl dd .form-control{width:100%}.callback-urls.has-many .callback-url-action-cell{display:table-cell}.callback-description{margin-top:20px}.callback-description .octicon{padding-left:0}.callback-url .label{display:none;width:64px;text-align:center}.callback-url.is-default-callback .label{display:inline-block}.callback-url.is-default-callback .btn{display:none}.callback-url-wrap{display:table;width:100%}.callback-url-field-cell{display:table-cell}.callback-url-action-cell{display:none;width:70px;text-align:right}.boxed-group.application-show-group dl.form-group>dd .form-control.wide{width:460px}.boxed-group.application-show-group dl.form-group>dd .form-control.short{height:50px;min-height:50px}.application-show-group .errored .note{display:none}.application-show-group .drag-and-drop{padding:0;text-align:left;background-color:transparent;border:0}.application-show-group .drag-and-drop img{margin-bottom:1px;vertical-align:bottom}.application-show-group .drag-and-drop span{padding:0}.application-show-group .dragover .logo-box{box-shadow:#c9ff00 0 0 3px}.application-show-group .is-uploading .loading{display:inline-block}.application-show-group .is-uploading .default{display:none}.application-show-group .is-failed .failed-request{display:inline-block}.application-show-group .is-failed .default{display:none}.application-show-group .is-bad-file .bad-file{display:inline-block}.application-show-group .is-bad-file .default{display:none}.application-show-group .is-too-big .file-too-big{display:inline-block}.application-show-group .is-too-big .default{display:none}.application-show-group .is-default .default{display:block}.security-history .security-history-timestamp{float:right;color:#767676}table.security-history-detail{width:100%;font-size:12px}table.security-history-detail td{max-width:200px;word-wrap:break-word}.org-two-factor .btn{float:right;margin:10px 0 0 20px}.org-two-factor .flash-global{margin-top:0}.two-factor-disabled .flash-global{display:block}.settings-email .email-actions .settings-remove-email{float:right;padding-right:7px;padding-left:7px;margin-left:5px;line-height:24px;color:#bd2c00;cursor:pointer}.settings-email .email-actions .settings-remove-email.settings-disabled-remove-email{display:block;color:#999}.settings-email .octicon-info{margin-left:5px}.settings-email .css-truncate-target{max-width:300px}.email-preference-exceptions{font-size:12px}.email-preference-exceptions h5{margin:15px 0 5px;color:#666}.email-preference-exceptions .exception-list{list-style:none}.email-preference-exceptions .exception{max-width:400px;padding:5px;padding-left:0;border-top:1px solid #eee}.email-preference-exceptions .exception:last-child{border-bottom:1px solid #eee}.email-preference-exceptions .exception-action{float:right}.email-preference-exceptions.opt-in-list{display:none}.transactional-only .email-preference-exceptions.opt-in-list{display:block}.transactional-only .email-preference-exceptions.opt-out-list{display:none}.two-factor-intro{width:675px;margin:40px auto 0}.two-factor-intro .two-factor-graphic{margin:20px 0}.two-factor-intro .two-factor-explain{padding:0;margin:0 0 40px;font-size:13px;list-style:none}.two-factor-intro .two-factor-explain li{float:left;padding:0;margin:0}.two-factor-intro .two-factor-explain .step-one{width:185px;margin-right:36px}.two-factor-intro .two-factor-explain .step-two{width:230px;margin-right:42px}.two-factor-intro .two-factor-explain .step-three{width:180px}.two-factor-graphic{width:675px;height:135px;background-image:url("/images/modules/settings/2fa_guide.png");background-repeat:no-repeat}.two-factor-recovery-codes{height:240px;padding-left:60px;margin-top:15px}.two-factor-recovery-code-mark{width:24px;height:24px;font-size:24px;line-height:16px;color:#bbb}.two-factor-recovery-code{display:inline-block;width:49%;line-height:1.1}.two-factor-recovery-code::before{position:relative;top:1px;margin-right:10px;font-size:26px;color:#eaeaea;content:"\25a1"}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){.two-factor-graphic{background-image:url("/images/modules/settings/2fa_guide@2x.png");background-size:675px 135px}}.sms-or-app{width:100%;padding:40px 0 0;margin:0;border-top:1px solid #ddd}.sms-or-app::before{display:table;content:""}.sms-or-app::after{display:table;clear:both;content:""}.sms-or-app li{float:left;width:325px;padding:0;list-style:none}.sms-or-app li:first-child{margin-right:25px}.sms-or-app li .btn{display:block;width:100%;height:100%;padding-top:12px;padding-bottom:12px;margin:10px 0;font-size:15px;text-align:center}.sms-or-app small{font-size:80%}.app-only{padding:20px 0 0}.app-only li{float:none;width:auto}.app-only li .btn{display:inline-block;width:auto;padding-right:20px;padding-left:20px}.two-factor-sms-main .octicon-alert{color:#bd2c00}.two-factor-sms-main .error-icon{position:relative;top:2px;left:5px;color:#bd2c00}.two-factor-sms-main .sent-message{position:relative;top:2px;left:5px;color:#6cc644}.container.two-factor-toggle{width:700px}.sms-country-code-field{padding-right:10px}.two-factor-toggle{margin-top:40px}.two-factor-toggle .two-factor-status{padding:20px 0;margin:0 0 20px;color:#767676;border-bottom:1px solid #eaeaea}.two-factor-toggle .two-factor-status p{margin:0}.two-factor-toggle .two-factor-status .btn{position:relative;top:-3px;float:right}.two-factor-toggle .two-factor-on{padding:3px 5px;margin-right:5px;color:#fff;text-shadow:0 1px 1px rgba(0,0,0,0.1);background-color:#6cc644;border-radius:2px}.two-factor-settings-group{position:relative;padding:0 0 20px 220px;margin:0 0 20px;border-bottom:1px solid #ddd}.two-factor-settings-group>h3{position:absolute;top:-15px;left:0;width:200px;font-size:14px}.two-factor-settings-group>h3 .octicon{position:absolute;left:-24px;color:#bd2c00}.two-factor-settings-group li{line-height:1.5;list-style:none}.u2f-registrations{padding-left:24px}.u2f-registration{position:relative;padding-bottom:8px;margin-bottom:8px;border-bottom:1px solid #f8f8f8}.u2f-registration.is-sending .u2f-registration-delete{display:none}.u2f-registration.is-sending .spinner{position:relative;top:3px}.u2f-registration-nickname{font-weight:bold}.u2f-registration-icon{position:absolute;left:-24px;color:#dac268}.new-u2f-registration{position:relative}.new-u2f-registration .add-u2f-registration-form{display:none;margin-bottom:10px}.new-u2f-registration.is-active .add-u2f-registration-link{display:none}.new-u2f-registration.is-active .add-u2f-registration-form{display:block}.new-u2f-registration .u2f-request-interaction,.new-u2f-registration .u2f-request-error{display:none}.new-u2f-registration.is-sending .u2f-request-interaction{display:block}.new-u2f-registration.is-showing-error .u2f-request-error{display:block}.new-u2f-registration .u2f-error-icon{font-size:64px}.u2f-box .u2f-sorry{display:block}.u2f-box .new-u2f-registration{display:none}.u2f-box.available .u2f-sorry{display:none}.u2f-box.available .new-u2f-registration{display:block}.github-access-banner{position:relative;padding:10px 20px 10px 70px;margin:0 0 20px;font-size:14px;border:1px solid #ddd;border-radius:3px}.github-access-banner .octicon{position:absolute;top:20px;left:20px;color:#bd2c00}.error-icon,.spinner,.sent-message,.sms-error-message,.text-code{display:none}.is-sending .spinner{display:inline-block}.is-sent .sent-message{display:inline-block}.is-not-sent .sms-error-message{display:block}.is-not-sent .error-icon{display:inline-block}.two-factor-secret{font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:13px}.two-factor-app-main{float:none !important;margin:0 auto}.qr-code-table{margin:40px auto}.qr-code-table tr{background:transparent;border:0}.qr-code-table th,.qr-code-table td{padding:0;border:0}.qr-code-table td{width:3px;height:3px}.qr-code-table .black{background:#000}.qr-code-table .white{background:#fff}.two-factor-actions{padding-top:25px;margin-top:25px;clear:both;border-top:1px solid #eaeaea}.two-factor-banner{position:relative;width:700px;padding-left:60px;margin:40px auto;color:#444;background:#fff;border:1px solid #ddd}.two-factor-banner:hover{border-color:#ddd}.two-factor-banner .octicon{position:absolute;top:15px;left:15px;color:#bd2c00}.two-factor-banner h2{line-height:32px}.two-factor-mini-banner{display:block;width:100%;padding:15px 15px 15px 42px;margin:0 0 20px;background:#fff}.two-factor-mini-banner .btn-sm{position:relative;top:-4px;float:right}.two-factor-mini-banner p{margin-bottom:0;line-height:1.5}.two-factor-mini-banner .octicon{position:absolute;top:15px;left:15px;color:#bd2c00}.orgs-settings{margin-bottom:15px}.confirmation-phrase{font-style:italic;font-weight:normal}.do-not-copy-me{-webkit-touch-callout:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}li.session-device{position:relative;padding:15px;line-height:18px;color:#767676;background-color:#fafafa}li.session-device .btn{float:right;margin-top:4px}li.session-device .session-state-indicator{float:left;width:8px;height:8px;margin-top:10px;border-radius:5px}li.session-device .session-state-indicator.recent{background-color:#6cc644;box-shadow:0 0 10px rgba(108,198,68,0.5)}li.session-device .session-state-indicator.not-recent{background-image:-webkit-linear-gradient(#aaa, #ccc);background-image:linear-gradient(#aaa, #ccc);box-shadow:0 1px 0 #fff}li.session-device .session-icon{float:left;width:32px;margin-top:1px;margin-left:15px;color:#bbb;text-align:center}li.session-device .sessions-more-info{position:relative;display:none;margin-top:10px}li.session-device.session-current{background-color:#fff}li.session-device.session-current .session-last-accessed{color:#767676}li.session-device.session-current .sessions-more-info{color:#767676}li.session-device.session-current .sessions-more-info::after{border-top-color:#fff}li.session-device.session-current .octicon{color:#767676}.session-details{position:relative;width:350px;margin-left:70px}.session-details:hover .octicon{color:#4078c0;cursor:pointer}.session-details.open .sessions-more-info{display:block}.session-title{display:block}.collaborators .collab-list{border-bottom-width:0}.collaborators .collab-list-item:first-child .collab-list-cell{border-top-width:0}.collaborators .collab-list-cell{padding-top:15px;padding-bottom:15px;vertical-align:middle}.collaborators .input-group.has-zeroclipboard-disabled{width:100%}.collaborators .input-group.has-zeroclipboard-disabled .form-control{border-radius:3px}.collaborators .input-group.has-zeroclipboard-disabled .input-group-button{display:none}.collaborators .collab-meta{width:140px}.collaborators .collab-remove{padding-right:20px;text-align:right}.collaborators .collab-remove .remove-link{color:#767676}.collaborators .collab-remove .remove-link:hover{color:#bd2c00}.collaborators .collab-team-link{width:300px}.collaborators .collab-team-link:hover{text-decoration:none}.collaborators .collab-team-link .avatar{float:left;margin-top:1px;margin-right:10px}.collaborators .collab-team-link.disabled{pointer-events:none}.collaborators .collab-info{height:100%;color:#666}.collaborators .collab-info .description{padding-right:50px;margin-top:3px;margin-bottom:3px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.collaborators .collab-info .collab-name{display:block;font-size:14px}.collaborators .collab-info .collab-message{position:relative;top:25%;display:block}.collaborators .copy-invite-modal{top:6px;width:352px}.collaborators .cancel-invite-cell{width:113px;padding-left:0}.access-sub-heading{float:right;font-weight:normal;line-height:1.4;color:#767676}.access-form-wrapper{padding:10px;background-color:#fcfcfc;border-top:1px solid #ddd;border-radius:0 0 3px 3px}.access-flash{padding:8px;margin-right:10px;margin-bottom:10px;margin-left:10px}.repo-access-group .blankslate{display:none}.repo-access-group.is-empty .blankslate{display:block}.repo-access-group.no-form .add-team-form{display:none}.repo-access-group .select-menu-item.has-access{display:none}.oauth-app-meta-container{display:inline-block;width:80%}.oauth-pending-deletion-list-item{background-color:#fafafa;box-shadow:inset 0 0 8px #eee}.oauth-pending-deletion-list-item:hover{background-color:#fafafa}.oauth-pending-deletion-list-item .oauth-pending-deletion{display:inline-block;width:19%;line-height:30px}.oauth-pending-deletion-list-item .active{display:none}.oauth-pending-deletion{display:none;width:100%}.boxed-group-list .access-level{color:#767676}.boxed-group-list .access-level.css-truncate-target{max-width:500px}.settings-next{font-size:14px;line-height:1.5}.settings-next label{font-size:14px}.settings-next .note{font-size:13px}.settings-next .form-checkbox input[type="radio"],.settings-next .form-checkbox input[type="checkbox"]{margin-top:4px}dl.form-group>dd textarea.compact{height:100px;min-height:0}.form-hr{margin-top:15px;margin-bottom:15px;border-bottom-color:#e5e5e5}.listgroup{list-style:none;border:1px solid #e5e5e5;border-radius:3px}.listgroup-item{min-height:inherit;padding:10px;font-size:13px;line-height:26px;color:#767676}.listgroup-item::before{display:table;content:""}.listgroup-item::after{display:table;clear:both;content:""}.listgroup-item+.listgroup-item{border-top:1px solid #e5e5e5}.listgroup-item.listgroup-item-preview{line-height:inherit}.listgroup-item.listgroup-item-preview .btn-group{margin-top:5px}.listgroup-item .css-truncate-target{max-width:615px}.listgroup-item-title{display:block;font-weight:bold}.listgroup-item-body{display:block}.listgroup-header{border-top:0;border-bottom:1px solid #e5e5e5}.listgroup-overflow{max-height:240px;overflow-y:auto;background-color:#f5f5f5}.listgroup-sm .listgroup-item{padding-top:5px;padding-bottom:5px}.protected-branches{margin-top:15px;margin-bottom:15px}.protected-branch-options{margin-left:20px;opacity:0.5}.protected-branch-options.active{opacity:1}.protected-branch-orgs-and-repo-admins{padding:10px;background-color:#fafafa}.authorized-pushers{width:440px}.authorized-pushers .add-protected-branch-user-or-team{display:block}.authorized-pushers .user-or-team-limit-reached{display:none;padding:10px;font-size:13px}.authorized-pushers.at-limit .add-protected-branch-user-or-team{display:none}.authorized-pushers.at-limit .user-or-team-limit-reached{display:block;width:440px}.protected-branch-authorized-pushers-table{margin-top:10px}.protected-branch-authorized-pushers-table .boxed-group-inner{max-height:350px;overflow-y:auto}.protected-branch-authorized-pushers-table .table-list{border-bottom:0}.protected-branch-authorized-pushers-table .table-list-cell{vertical-align:middle}.protected-branch-authorized-pushers-table .table-list-cell:first-child{width:100%}.protected-branch-authorized-pushers-table .avatar,.protected-branch-authorized-pushers-table .octicon-jersey,.protected-branch-authorized-pushers-table .octicon-organization{width:36px;margin-right:10px;text-align:center}.protected-branch-pusher{color:#333}.user-already-added::after{display:inline-block;padding:1px 5px;margin-left:6px;font-size:11px;line-height:1.4;color:#fff;content:"Already added";background-color:#c9510c;border-radius:3px}.protected-branch-admin-permission.active{float:left;padding:1px 3px;margin:-2px 0 -2px -4px;border:1px solid transparent;border-radius:3px;-webkit-animation:toggle-color 1s ease-in-out 0s;animation:toggle-color 1s ease-in-out 0s}@-webkit-keyframes toggle-color{0%{background-color:transparent}50%{color:#4c4a42;background-color:#fff9ea;border-color:#dfd8c2}100%{background-color:transparent}}@keyframes toggle-color{0%{background-color:transparent}50%{color:#4c4a42;background-color:#fff9ea;border-color:#dfd8c2}100%{background-color:transparent}}.automated-check-options{margin-top:10px}.automated-check-options .listgroup-item label{font-size:inherit}.automated-check-options .listgroup-item input[type="checkbox"]{float:none;margin-top:-2px;margin-right:5px;margin-left:0}.automated-check-options .label{margin-top:4px}.list-group-text-block{position:relative;padding-left:50px;line-height:20px}.list-group-text-block .meta-title{display:block;font-weight:bold;color:#000}.list-group-text-block .meta-description{display:inline-block;width:560px}.list-group-text-block img{position:absolute;top:0;left:0}.nudge-down.btn-group{position:relative;top:6px}.repository-merge-features .form-group.errored label{color:inherit}.repository-merge-features .form-group.errored .error{position:inherit;padding:0;margin-top:0;margin-left:6px;font-size:11px;color:#bd2c00;background:transparent;border:0}.repository-merge-features .form-group.errored .error::before,.repository-merge-features .form-group.errored .error::after{display:none}dl.form-group.cname code{padding:0.2em;font-size:85%;background-color:rgba(0,0,0,0.04);border-radius:3px}.logged-out.signup .header-logged-out .container,.logged-out.signup .site-footer{width:750px}.logged-out.signup .site-footer{margin-right:auto;margin-left:auto}.logged-out.signup .site-footer .octicon-mark-github{top:30px}.logged-out.signup .header-actions .primary,.logged-out.signup .site-footer-links,.logged-out.signup .site-search{display:none}.setup-wrapper{width:750px;padding-top:30px;margin:0 auto}.setup-wrapper::before{display:table;content:""}.setup-wrapper::after{display:table;clear:both;content:""}.setup-header{padding-bottom:20px;margin:0 auto 30px;overflow:hidden;text-align:left;text-shadow:0 1px 0 white;border-bottom:1px solid #ddd}.setup-header h1{margin-top:0;margin-bottom:0;font-size:45px;font-weight:normal;line-height:1.1;letter-spacing:-1px}.setup-header h1 .octicon{color:#bbb}.setup-header .lead{margin-top:2px;margin-bottom:0;font-size:21px}.setup-header .lead a{color:#767676}.setup-header .lead a:hover{color:#4078c0;text-decoration:none}.setup-org{padding-bottom:0;border-bottom:0}.setup-main{float:left;width:450px}.setup-main.without-secondary{margin-left:150px}.setup-secondary{float:right;width:250px}.setup-secondary .info{padding-top:0;padding-bottom:0;margin-top:-10px;font-size:12px;line-height:18px;color:#767676;text-align:center}.setup-info-module{margin-bottom:30px;background-color:#fff;border:1px solid #ccc;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.075)}.setup-info-module h2{padding:15px;margin-bottom:15px;overflow:hidden;font-size:16px;border-bottom:1px solid #ddd}.setup-info-module h2 .price{float:right;font-weight:bold;color:#767676}.setup-info-module h3{padding:0 15px;margin:0 0 -7px;font-size:14px}.setup-info-module p{padding:0 15px;margin:15px 0}.setup-info-module .setup-section-title{margin-bottom:10px}.setup-info-module .setup-info-note{padding:1px 0;margin:0;background:#f9f9f9;border-top:1px solid #e0e0e0}.features-list{padding:0 15px 15px;margin:0;font-size:14px;list-style:none}.features-list li{margin-top:10px}.features-list li:first-child{margin-top:0}.features-list .list-divider{margin:15px -15px;border-top:1px solid #eee}.features-list .octicon-check{margin-right:5px;color:#60b044}.features-list .octicon-question{font-size:12px;color:#555}.features-list .tooltipped::after{width:250px;white-space:normal}.features-list.features-list-org{padding-bottom:0}.seat-breakdown-list{padding:0 15px 0 25px;margin:0;list-style:none}.seat-breakdown-list .subtle-link{color:#767676}.setup-form-container .setup-form-title{font-size:16px}.setup-form-container .secure{float:right;margin-top:2px;font-size:11px;color:#60b044;text-transform:uppercase}.setup-form-container hr{margin-top:25px;margin-bottom:25px}.setup-form-container .form-actions{padding-top:0;padding-bottom:0;text-align:left}.team-member-container{margin-bottom:20px}.team-member-container .team-member-username{line-height:1.2}.setup-form{padding-bottom:15px}.setup-form .form-group dd .form-control{width:100%}.setup-form .form-group dd .form-control.short{width:250px}.setup-form dd{position:relative}.setup-form dd .octicon{position:absolute;top:8px;right:25px}.setup-form .octicon-alert{color:#bd2c00}.setup-form .octicon-check{color:#6cc644}.setup-form .text-muted{margin-top:5px}.setup-form .tos-info,.setup-form .setup-organization-next{margin:15px 0;border-top:1px solid #eee;border-bottom:1px solid #eee}.setup-form .tos-info{padding:15px 0}.setup-form .setup-organization-next{padding-top:15px;padding-bottom:15px}.setup-form .setup-plans{margin-bottom:25px;overflow:hidden;border-collapse:separate;border:solid 1px #ccc;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.075)}.setup-form .setup-plans tr.selected{background-color:#f0f7fd}.setup-form .setup-plans tr.selected.no-choice{background-color:inherit}.setup-form .setup-plans th,.setup-form .setup-plans td{vertical-align:middle;border-bottom:1px solid #ccc}.setup-form .setup-plans .name{font-weight:bold}.setup-form .setup-plans .choose-plan input[type="radio"]{display:none}.setup-creditcard-form .cc-extras{margin-bottom:15px}.setup-creditcard-form .expiration-form{width:120px}.setup-creditcard-form .expiration-form,.setup-creditcard-form .cvv-form,.setup-creditcard-form .country-form,.setup-creditcard-form .state-form{float:left;margin:0;word-wrap:normal}.setup-creditcard-form .country-form,.setup-creditcard-form .postal-code-form{margin-top:0;margin-bottom:15px}.setup-creditcard-form .form-group dd .input-cvv{width:168px}.setup-creditcard-form .form-group select.select-country{width:182px;margin-right:5px}.setup-creditcard-form .form-group select.select-state{width:101px}.setup-creditcard-form .form-group .card-select-number-field,.setup-creditcard-form .form-group .input-postal-code,.setup-creditcard-form .form-group .input-vat{width:288px}.setup-creditcard-form.is-vat-country .vat-field{display:block}.setup-creditcard-form.is-international .form-group select.select-country{width:288px}.setup-creditcard-form.is-international .state-form{display:none}.setup-creditcard-form.no-postcodes .postal-code-form{display:none}.setup-creditcard-form dd .octicon-credit-card{position:inherit}.setup-creditcard-form .enter-new-card{display:none}.setup-creditcard-form.has-credit-card .enter-new-card{display:inline-block}.setup-creditcard-form.has-credit-card .card-select-number-field,.setup-creditcard-form.has-credit-card .cancel-enter-new-card,.setup-creditcard-form.has-credit-card .cards-select{display:none}.setup-creditcard-form .vat-field{display:none}.setup-creditcard-form .vat-field.prefilled{display:block}.setup-creditcard-form .help-text{font-size:80%;font-weight:normal;color:#767676}.user-identification-questions{float:none;width:auto;margin-top:40px}.user-identification-questions .question{margin-bottom:30px}.user-identification-questions .question-title{padding-right:40px;margin-bottom:10px;font-size:14px;line-height:1.5}.user-identification-questions .question-title i{font-size:13px;font-style:normal;font-weight:normal;color:#767676}.user-identification-questions .response-group label{font-weight:normal}.user-identification-questions .form-checkbox{float:left;width:250px;margin:8px 0}.user-identification-questions .other-field .form-control{margin-top:10px}.user-identification-questions .other-field input+.form-control{display:none}.user-identification-questions .other-field input:checked+.form-control{display:block}.user-identification-questions .alternate-action{margin-left:10px;line-height:34px}.user-identification-questions .disclaimer{margin:40px 0 0;text-align:center}.showcase-page-pattern{position:relative;z-index:-1;height:100px;margin-top:-21px;margin-bottom:-70px}.showcase-page-pattern::after{position:absolute;top:0;right:0;bottom:0;left:0;display:block;content:"";background-image:-webkit-linear-gradient(270deg, rgba(255,255,255,0.85), #fff);background-image:linear-gradient(180deg, rgba(255,255,255,0.85), #fff)}.showcase-page-header{padding-bottom:50px;margin-bottom:20px;border-bottom:1px solid #e5e5e5}.showcase-page-header .container{position:relative}.showcase-page-header .draft-tag{position:absolute;top:-40px;left:0;background-color:rgba(0,0,0,0.65);border-bottom-right-radius:4px;border-bottom-left-radius:4px}.showcase-page-header .draft-tag:hover{text-decoration:none;background-color:rgba(0,0,0,0.85)}.showcase-page-title{margin-bottom:5px;font-size:40px;font-weight:300}.showcase-page-description{max-width:75%;margin-bottom:15px}.showcase-page-meta .meta-info{margin-right:20px}.showcase-page-repo-list{border-top:1px solid #eee}.showcase-page-sidebar{padding-left:40px}.showcase-page-sidebar .subnav-search{margin-left:0}.showcase-page-sidebar .subnav-search-input{width:100%}.showcase-page-sidebar .other-content-title{margin-bottom:10px;font-weight:normal}.collection-page .column.main{margin-right:260px !important}.collection-page .column.sidebar{width:240px}.collection-page .other-content-title{margin-top:40px}.collection-page .other-content-title:first-child{margin-top:0}.collection-search-results em{padding:0.1em;background-color:#faffa6}.collection-search-result{margin-bottom:40px;list-style-type:none}.collection-search-result-title{margin-top:0}.collection-search-page .search-results-info{float:right;margin-left:10px;font-size:15px;line-height:33px}.draft-tag{padding:5px 10px;font-weight:bold;color:#eee;background-color:#404040}.collection-listing-search{margin-bottom:20px}.collection-listing-search .subnav-search{margin-right:25%;margin-left:0}.pinned-repos-spinner{position:relative;top:2px;left:6px}.pinned-repos-reorder-error{padding-left:6px;font-size:12px;font-weight:normal;color:#911}.pinned-repo-list-item{display:-webkit-box;display:flex;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border-top:1px solid #e5e5e5}.pinned-repo-list-item:first-child{border-top:0}.pinned-repo-list-item .pinned-repository-handle:hover{cursor:grab;cursor:-webkit-grab}.pinned-repo-list-item.is-dragging,.pinned-repo-list-item.is-dragging .pinned-repository-handle{cursor:grabbing;cursor:-webkit-grabbing}.pinned-repo-list-item.is-dragging{background-color:#f2f8fa}.pinned-repo-list-item.sortable-ghost{background-color:#f2f8fa;opacity:0}.pinned-repo-list-item .repo-icon{display:-webkit-box;display:flex;width:32px;-webkit-box-pack:center;justify-content:center;padding-top:8px}.pinned-repo-list-item.no-description .repo-icon,.pinned-repo-list-item.reorderable .repo-icon{-webkit-box-align:center;align-items:center;padding-top:0}.pinned-repo-link{display:-webkit-box;display:flex;-webkit-box-flex:1;flex-grow:1;padding-top:6px;padding-bottom:6px}.pinned-repo-link:hover{text-decoration:none}.pinned-repo-link .repo-info{display:-webkit-box;display:flex;min-height:42px;-webkit-box-orient:vertical;-webkit-box-direction:normal;flex-direction:column;-webkit-box-pack:center;justify-content:center;-webkit-box-flex:1;flex-grow:1}.pinned-repo-link .repo-and-owner{max-width:600px;font-size:14px}.pinned-repo-link:hover .repo-and-owner{text-decoration:underline}.pinned-repo-link .owner{max-width:110px}.pinned-repo-link .repo{font-weight:bold}.pinned-repo-link .repo-description{max-width:600px;font-size:12px;line-height:21px;color:#767676}.pinned-repo-link .stars{align-self:center;padding-right:10px;font-size:12px;color:#888}.pinned-repo-link .stars .octicon{vertical-align:bottom}.pinned-repos-setting-link{font-size:13px;font-weight:normal}.facebox .pinned-repos-selection-list{max-height:250px;margin-left:0;overflow:auto;list-style:none}.pinned-repos-selection-list-item{display:table;width:100%;color:#767676;table-layout:fixed}.pinned-repos-selection-list-item .pinned-repo-name,.pinned-repos-selection-list-item .stars{display:table-cell;vertical-align:middle}.pinned-repos-selection-list-item .pinned-repo-name{width:76%;padding:5px;font-weight:bold}.pinned-repos-selection-list-item .pinned-repo-name .css-truncate-target{max-width:90%}.pinned-repos-selection-list-item .stars{width:24%;padding:5px 5px 5px 0;font-size:13px;color:#999;text-align:right;white-space:nowrap}.pinned-repos-selection-list-item.selected{color:#333;background-color:#f2f8fa}.pinned-repo-filters{padding:15px;margin:0 -15px 15px -15px;border-top:1px solid #e5e5e5;border-bottom:1px solid #e5e5e5}.traffic-graph{min-height:150px}.traffic-graph .activity{margin-top:0}.traffic-graph .activity .dots{margin-top:40px}.traffic-graph .path{fill:none;stroke-width:2}.traffic-graph path.total{stroke:#1db34f}.traffic-graph path.unique{stroke:#1d7fb3}.traffic-graph .axis .tick:first-child line{stroke:#1db34f;stroke-width:2px}.traffic-graph .y line{stroke:#1db34f}.traffic-graph .y.unique line{stroke:#1d7fb3}.traffic-graph .overlay{fill-opacity:0}.uniques-graph .axis .tick:nth-child(14) line{stroke:#1d7fb3;stroke-width:2px}.svg-tip .date{color:#fff}.top-domains .dots{display:block;margin:167px auto 0}.top-domains-icon{display:inline-block;margin-right:5px;vertical-align:middle}table.capped-list{width:100%;line-height:100%}table.capped-list th{padding:8px;text-align:left;background:#f4f4f4;border-bottom:1px solid #ddd}table.capped-list td{padding:8px;font-size:12px;border-bottom:1px solid #eee}table.capped-list th.middle,table.capped-list td.middle{text-align:center}table.capped-list .favicon{width:16px;height:16px;margin:0 5px;vertical-align:middle}table.capped-list .octicon{margin-right:10px;color:#555;vertical-align:-1px}table.capped-list tr:nth-child(even){background-color:#fcfcfc}table.capped-list.mini-icons .mini-icon{margin-right:5px;color:#555}.capped-list-label{max-width:200px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.traffic-graph-stats{border-top:1px solid #ddd}.traffic-graph-stats .summary-stats{width:100%}.traffic-graph-stats .summary-stats::before{display:table;content:""}.traffic-graph-stats .summary-stats::after{display:table;clear:both;content:""}.traffic-graph-stats .summary-stats li{display:block;float:left;width:50%;padding-bottom:10px}.totals circle{fill:#1db34f;stroke:#fff;stroke-width:2}.uniques circle{fill:#1d7fb3;stroke:#fff;stroke-width:2}.top-lists .is-loading{margin:40px;text-align:center}ul.web-views li{width:140px}ul.clones li{width:170px}.tree-browser{width:100%;margin:0;border-right:0;border-bottom:1px solid #cacaca;border-left:0}.tree-browser td{padding:7px 3px;color:#484848;white-space:nowrap;vertical-align:middle;background:#f8f8f8;border-bottom:1px solid #eee}.tree-browser td.icon{width:17px;padding-right:2px;padding-left:10px}.tree-browser td:first-child{border-left:1px solid #cacaca}.tree-browser td:last-child{border-right:1px solid #cacaca}.tree-browser td a.message{color:#484848}.tree-browser td span.ref{color:#aaa}.tree-browser img{vertical-align:text-bottom}.tree-browser tbody tr:last-child td{border-bottom:0}.tree-browser .history{float:right;padding-right:5px}.tree-browser .octicon-chevron-right{color:transparent}.tree-browser tr.navigation-focus td{background-color:#fff}.tree-browser tr.navigation-focus td .octicon-chevron-right{color:#4078c0}.tree-browser .octicon-file-directory{color:#80a6cd}.tree-browser .octicon-file-submodule{color:#3cbf5e}.tree-browser .octicon-file-text{color:#767676}.tree-browser .content{max-width:220px}.tree-browser .message{max-width:420px}.tree-browser .css-truncate-target{max-width:100%}.tree-browser-result-template{display:none}.tree-browser-result .css-truncate-target{max-width:870px}.tree-browser-result mark{font-weight:bold;color:#4078c0;background-color:transparent}.tree-finder-input,.tree-finder-input:focus{position:relative;top:1px;height:22px;min-height:0;padding:0;margin-left:5px;font-size:100%;line-height:1px;vertical-align:top;border:0;outline:none;box-shadow:none;-webkit-appearance:none;-moz-appearance:none;appearance:none}.tree-finder .no-results{display:none}.tree-finder .no-results th{text-align:center}.tree-finder tr td.icon{cursor:pointer}.tree-finder .tree-browser{border-top:1px solid #cacaca}.filterable-empty+.no-results{display:block}.wiki-wrapper.history .gollum-footer ul.actions li{margin:0 0.6em 0 0}.wiki-actions{display:block;padding:0;overflow:hidden;list-style-type:none}.results .wiki-actions li{margin:0 1em 0 0}.compare .wiki-actions{margin-bottom:1.4em}.compare .wiki-actions li{margin-right:0.6em;margin-left:0}.wiki-body{margin-top:20px}.wiki-body .markdown-body{padding:0 30px;margin:0 -30px}.wiki-rightbar{float:right;width:230px}.wiki-rightbar .markdown-body{font-size:13px}.wiki-rightbar .markdown-body .anchor{display:none}.wiki-rightbar .markdown-body h1{padding-bottom:5px;font-size:1.6em;line-height:1.2;border-color:#eee}.wiki-rightbar .markdown-body h2{padding-bottom:5px;font-size:1.4em;line-height:1.2;border-color:#eee}.wiki-rightbar .markdown-body h3,.wiki-rightbar .markdown-body h4,.wiki-rightbar .markdown-body h5,.wiki-rightbar .markdown-body h6{font-size:1.2em;line-height:1.2;border-color:#eee}.wiki-rightbar .boxed-group>h3{cursor:pointer}.wiki-rightbar .boxed-group .caret-collapsed{display:none}.wiki-rightbar .boxed-group.collapsed .caret-expanded{display:none}.wiki-rightbar .boxed-group.collapsed .caret-collapsed{display:inline}.wiki-rightbar .boxed-group.collapsed>h3{border-bottom:1px solid #d8d8d8;border-radius:3px}.wiki-rightbar .boxed-group.collapsed .boxed-group-inner{display:none}.wiki-rightbar p:last-child,.wiki-rightbar ul:last-child,.wiki-rightbar ol:last-child{margin-bottom:0}.wiki-pages{padding:0;margin:0;list-style-type:none}.wiki-page-link{display:block;padding:6px 10px;word-wrap:break-word}.has-rightbar .wiki-body,.has-rightbar .wiki-footer{margin-right:280px}.wiki-footer{margin:20px 0 50px;clear:both}.wiki-footer .markdown-body{font-size:13px}.wiki-wrapper .blankslate.wiki{padding:115px 0}.wiki-wrapper .blankslate.wiki p.has-fixed-width{text-align:center}.wiki-wrapper .gh-header .divider{padding:0 3px 0 2px}.wiki-wrapper .gh-header-meta{padding-bottom:15px;margin-top:6px}.wiki-wrapper a.history{color:inherit}.wiki-wrapper a.history:hover{color:#555}.wiki-wrapper.edit h1{font-weight:normal;color:inherit}.wiki-wrapper.edit h1 strong{color:#000}.wiki-wrapper .wiki-empty-box{display:block;padding:10px 0;margin:20px 0;color:#767676;text-align:center;border:1px dashed #ddd;border-radius:3px;-webkit-transition:color 0.1s ease-in-out, border-color 0.1s ease-in-out;transition:color 0.1s ease-in-out, border-color 0.1s ease-in-out}.wiki-wrapper .wiki-empty-box .octicon-plus{margin-right:4px;opacity:0.4}.wiki-wrapper .wiki-empty-box:hover{color:#767676;text-decoration:none;border-color:#ccc}.wiki-wrapper .wiki-auxiliary-content{background:#f6f6f6;box-shadow:0 1px 2px rgba(0,0,0,0.06)}.wiki-wrapper .wiki-auxiliary-content.markdown-body.wiki-writable>*:nth-child(2){margin-top:0 !important}.wiki-wrapper .wiki-auxiliary-content.markdown-body img{background:none}.wiki-wrapper .wiki-auxiliary-content .wiki-edit-link{position:relative;z-index:2;float:right;color:#767676;opacity:0.2;-webkit-transition:opacity 0.2s ease-in-out;transition:opacity 0.2s ease-in-out}.wiki-wrapper .wiki-auxiliary-content .wiki-edit-link:hover{text-decoration:none;opacity:1}.wiki-wrapper .wiki-auxiliary-content-no-bg{background:#fff}.wiki-wrapper .wiki-custom-sidebar{padding:10px;margin-bottom:20px;border:solid 1px #e2e2e2;border-radius:3px}.wiki-wrapper .wiki-custom-sidebar>:nth-child(2){margin-top:0}.wiki-wrapper .wiki-custom-sidebar .octicon-pencil{position:relative;z-index:10;float:right;margin-left:15px;color:#767676}.wiki-wrapper .wiki-custom-sidebar .octicon-pencil:hover{color:#333;text-decoration:none}.wiki-wrapper .wiki-footer{margin:30px 30px 0;clear:none}.wiki-wrapper .wiki-footer .markdown-body{padding:10px 15px}.wiki-wrapper .wiki-footer .wiki-empty-box{margin:0 -30px}.wiki-wrapper .wiki-footer .wiki-edit-link{right:-5px}.wiki-wrapper .wiki-footer .wiki-auxiliary-content{border-top-left-radius:3px;border-top-right-radius:3px}.wiki-wrapper.compare .gh-header{margin-bottom:20px}.wiki-wrapper .wiki-history{margin-top:20px}.wiki-wrapper .wiki-history .checkbox{width:30px;text-align:center}.wiki-wrapper .wiki-history .author{width:200px}.wiki-wrapper .wiki-history .author img{display:block;float:left;margin-right:6px}.wiki-wrapper .wiki-history .date{color:#bbb;white-space:nowrap}.wiki-wrapper .wiki-history .commit{max-width:450px;overflow:hidden;text-overflow:ellipsis}.wiki-wrapper .wiki-history .commit-meta{width:160px;padding-right:10px;text-align:right;white-space:nowrap}.wiki-wrapper .wiki-history .commit-meta code{display:inline-block;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;line-height:16px;vertical-align:top}.wiki-wrapper .wiki-history .commit-id{color:#bbb}.wiki-wrapper .wiki-history .commit-id:hover{color:#4078c0}.wiki-wrapper .wiki-rightbar .sidebar-button{margin-top:10px}.wiki-wrapper .wiki-content{clear:both}.wiki-wrapper .wiki-content .markdown-body{word-break:break-word}.wiki-wrapper .wiki-content .gollum-editor-title-field{margin:0 0 14px}.wiki-wrapper .wiki-content .file-wrap{margin-top:20px;border-top:1px solid #ddd;border-radius:3px}.wiki-wrapper .wiki-content .file-wrap .files{border-radius:3px}.wiki-pages-box .wiki-more-pages{display:none}.wiki-pages-box.wiki-show-more .wiki-more-pages,.wiki-pages-box .filterable-active .wiki-more-pages{display:block}.wiki-pages-box.wiki-show-more .wiki-more-pages-link,.wiki-pages-box .filterable-active .wiki-more-pages-link{display:none}.wiki-pages-box .wiki-more-pages-link{box-shadow:inset 0 1px 0 #e5e5e5}.wiki-pages-box .wiki-more-pages-link a{display:block;padding:3px;color:#7aa1d3;text-align:center}.wiki-pages-box .wiki-more-pages-link a:hover{color:#4078c0;text-decoration:none} + + +/*! normalize.css v4.1.1 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,details,figcaption,figure,footer,header,main,menu,nav,section,summary{display:block}audio,canvas,progress,video{display:inline-block}audio:not([controls]){display:none;height:0}progress{vertical-align:baseline}template,[hidden]{display:none}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}dfn{font-style:italic}h1{font-size:2em;margin:0.67em 0}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-0.25em}sup{top:-0.5em}img{border-style:none}svg:not(:root){overflow:hidden}code,kbd,pre,samp{font-family:monospace, monospace;font-size:1em}figure{margin:1em 40px}hr{box-sizing:content-box;height:0;overflow:visible}button,input,select,textarea{font:inherit;margin:0}optgroup{font-weight:bold}button,input{overflow:visible}button,select{text-transform:none}button,html [type="button"],[type="reset"],[type="submit"]{-webkit-appearance:button}button::-moz-focus-inner,[type="button"]::-moz-focus-inner,[type="reset"]::-moz-focus-inner,[type="submit"]::-moz-focus-inner{border-style:none;padding:0}button:-moz-focusring,[type="button"]:-moz-focusring,[type="reset"]:-moz-focusring,[type="submit"]:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid #c0c0c0;margin:0 2px;padding:0.35em 0.625em 0.75em}legend{box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}textarea{overflow:auto}[type="checkbox"],[type="radio"]{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-input-placeholder{color:inherit;opacity:0.54}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}*{box-sizing:border-box}input,select,textarea,button{font-family:inherit;font-size:inherit;line-height:inherit}body{font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-size:14px;line-height:1.5;color:#333;background-color:#fff}a{color:#4078c0;text-decoration:none}a:hover,a:active{text-decoration:underline}hr,.rule{height:0;margin:15px 0;overflow:hidden;background:transparent;border:0;border-bottom:1px solid #ddd}hr::before,.rule::before{display:table;content:""}hr::after,.rule::after{display:table;clear:both;content:""}table{border-spacing:0;border-collapse:collapse}td,th{padding:0}button{cursor:pointer}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:0;line-height:1.5}h1{font-size:32px;font-weight:600}h2{font-size:24px;font-weight:600}h3{font-size:20px;font-weight:600}h4{font-size:16px;font-weight:600}h5{font-size:14px;font-weight:600}h6{font-size:12px;font-weight:600}p{margin-top:0;margin-bottom:10px}small{font-size:90%}blockquote{margin:0}ul,ol{padding-left:0;margin-top:0;margin-bottom:0}ol ol,ul ol{list-style-type:lower-roman}ul ul ol,ul ol ol,ol ul ol,ol ol ol{list-style-type:lower-alpha}dd{margin-left:0}tt,code{font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px}pre{margin-top:0;margin-bottom:0;font:12px Consolas, "Liberation Mono", Menlo, Courier, monospace}.pl-c{color:#969896}.pl-c1,.pl-s .pl-v{color:#0086b3}.pl-e,.pl-en{color:#795da3}.pl-smi,.pl-s .pl-s1{color:#333}.pl-ent{color:#63a35c}.pl-k{color:#a71d5d}.pl-s,.pl-pds,.pl-s .pl-pse .pl-s1,.pl-sr,.pl-sr .pl-cce,.pl-sr .pl-sre,.pl-sr .pl-sra{color:#183691}.pl-v{color:#ed6a43}.pl-id{color:#b52a1d}.pl-ii{color:#f8f8f8;background-color:#b52a1d}.pl-sr .pl-cce{font-weight:bold;color:#63a35c}.pl-ml{color:#693a17}.pl-mh,.pl-mh .pl-en,.pl-ms{font-weight:bold;color:#1d3e81}.pl-mq{color:#008080}.pl-mi{font-style:italic;color:#333}.pl-mb{font-weight:bold;color:#333}.pl-md{color:#bd2c00;background-color:#ffecec}.pl-mi1{color:#55a532;background-color:#eaffea}.pl-mdr{font-weight:bold;color:#795da3}.pl-mo{color:#1d3e81}.ace_gutter{background:#ffffff;color:#999999}.ace_print-margin{width:1px;background:#e8e8e8}.ace-github-light{background-color:#ffffff;color:#333333}.ace_cursor{color:#000000}.ace_marker-layer .ace_selection{background:#c8c8fa}.ace_multiselect .ace_selection.ace_start{box-shadow:0 0 3px 0px #ffffff;border-radius:2px}.ace_marker-layer .ace_step{background:#c6dbae}.ace_marker-layer .ace_bracket{margin:-1px 0 0 -1px;border:1px solid #c0c0c0}.ace_marker-layer .ace_active-line{background:#f5f5f5}.ace_gutter-active-line{background-color:#f5f5f5}.ace_marker-layer .ace_selected-word{border:1px solid #c8c8fa}.ace_fold{background-color:#a71d5d;border-color:#333333}.ace_keyword{color:#a71d5d}.ace_constant{color:#0086b3}.ace_support{color:#0086b3}.ace_support.ace_constant{color:#0086b3}.ace_support.ace_type{color:#a71d5d}.ace_storage{color:#a71d5d}.ace_storage.ace_type{color:#a71d5d}.ace_invalid.ace_illegal{text-decoration:underline;font-style:italic;color:#f8f8f8;background-color:#b52a1d}.ace_invalid.ace_deprecated{text-decoration:underline;font-style:italic;color:#b52a1d}.ace_string{color:#183691}.ace_string.ace_regexp{color:#183691}.ace_comment{color:#969896}.ace_variable{color:#ed6a43}.ace_entity.ace_name{color:#795da3}.ace_entity.ace_name.ace_tag{color:#63a35c}.ace_markup.ace_heading{color:#1d3e81}.ace_markup.ace_list{color:#693a17}.octicon{display:inline-block;vertical-align:text-top;fill:currentColor}.flash{position:relative;padding:15px;font-size:14px;line-height:1.5;color:#246;background-color:#e2eef9;border:1px solid #bac6d3;border-radius:3px}.flash p:last-child{margin-bottom:0}.flash-messages{margin-bottom:20px}.flash-close{float:right;width:34px;height:44px;margin:-11px;line-height:40px;color:inherit;text-align:center;cursor:pointer;background:none;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none;opacity:0.6}.flash-close:hover{opacity:1}.flash-action{float:right;margin-top:-4px;margin-left:20px}.flash-warn{color:#4c4a42;background-color:#fff9ea;border-color:#dfd8c2}.flash-error{color:#911;background-color:#fcdede;border-color:#d2b2b2}.flash-full{margin-top:-1px;border-width:1px 0;border-radius:0}.flash-with-icon .container{padding-left:40px}.flash-with-icon .flash-icon{float:left;margin-top:3px;margin-left:-25px}.flash-content{margin-top:0;margin-bottom:0;line-height:1.5}.warning{padding:0.5em;margin-bottom:0.8em;font-weight:bold;background-color:#fffccc}.avatar{display:inline-block;overflow:hidden;line-height:1;vertical-align:middle;border-radius:3px}.avatar-small{border-radius:2px}.avatar-link{float:left;line-height:1}.avatar-group-item{display:inline-block;margin-bottom:3px}.avatar-parent-child{position:relative}.avatar-child{position:absolute;right:-15%;bottom:-9%;background-color:#fff;border-radius:2px;box-shadow:-2px -2px 0 rgba(255,255,255,0.8)}.avatar-stack{display:inline-block;white-space:nowrap}.avatar-stack .avatar{position:relative;z-index:2;display:inline-block;width:20px;height:20px;margin-right:-15px;background-color:#fff;border-right:1px solid #fff;border-radius:2px;-webkit-transition:margin 0.1s ease-in-out;transition:margin 0.1s ease-in-out}.avatar-stack .avatar:first-child{z-index:3}.avatar-stack .avatar:last-child{z-index:1;margin-right:0}.avatar-stack:hover .avatar{margin-right:3px}.avatar-stack:hover .avatar:last-child{margin-right:0}.blankslate{position:relative;padding:30px;text-align:center;background-color:#fafafa;border:1px solid #e5e5e5;border-radius:3px;box-shadow:inset 0 0 10px rgba(0,0,0,0.05)}.blankslate code{padding:2px 5px 3px;font-size:14px;background:#fff;border:1px solid #eee;border-radius:3px}.blankslate-icon{margin-right:5px;margin-bottom:10px;margin-left:5px;color:#aaa}.blankslate-capped{border-radius:0 0 3px 3px}.blankslate-spacious{padding:100px 60px 120px}.blankslate-narrow{width:485px;margin:0 auto}.blankslate-large h3{margin:0.75em 0;font-size:20px}.blankslate-large p{font-size:16px}.blankslate-large p.has-fixed-width{width:540px;margin:0 auto;text-align:left}.blankslate-clean-background{background:none;border:0;box-shadow:none}.btn{position:relative;display:inline-block;padding:6px 12px;font-size:14px;font-weight:600;line-height:20px;color:#333;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:#eee;background-image:-webkit-linear-gradient(#fcfcfc, #eee);background-image:linear-gradient(#fcfcfc, #eee);border:1px solid #d5d5d5;border-radius:3px;-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn i{font-style:normal;font-weight:500;opacity:0.6}.btn .octicon{vertical-align:text-top}.btn .counter{text-shadow:none;background-color:#e5e5e5}.btn:focus{text-decoration:none;border-color:#51a7e8;outline:none;box-shadow:0 0 5px rgba(81,167,232,0.5)}.btn:focus:hover,.btn.selected:focus{border-color:#51a7e8}.btn:hover,.btn:active,.btn.zeroclipboard-is-hover,.btn.zeroclipboard-is-active{text-decoration:none;background-color:#ddd;background-image:-webkit-linear-gradient(#eee, #ddd);background-image:linear-gradient(#eee, #ddd);border-color:#ccc}.btn:active,.btn.selected,.btn.zeroclipboard-is-active{background-color:#dcdcdc;background-image:none;border-color:#b5b5b5;box-shadow:inset 0 2px 4px rgba(0,0,0,0.15)}.btn.selected:hover{background-color:#cfcfcf}.btn:disabled,.btn:disabled:hover,.btn.disabled,.btn.disabled:hover{color:rgba(102,102,102,0.5);cursor:default;background-color:rgba(229,229,229,0.5);background-image:none;border-color:rgba(197,197,197,0.5);box-shadow:none}.btn-primary{color:#fff;text-shadow:0 -1px 0 rgba(0,0,0,0.15);background-color:#60b044;background-image:-webkit-linear-gradient(#8add6d, #60b044);background-image:linear-gradient(#8add6d, #60b044);border-color:#5ca941}.btn-primary .counter{color:#60b044;background-color:#fff}.btn-primary:hover{color:#fff;background-color:#569e3d;background-image:-webkit-linear-gradient(#79d858, #569e3d);background-image:linear-gradient(#79d858, #569e3d);border-color:#4a993e}.btn-primary:active,.btn-primary.selected{text-shadow:0 1px 0 rgba(0,0,0,0.15);background-color:#569e3d;background-image:none;border-color:#418737}.btn-primary.selected:hover{background-color:#4c8b36}.btn-primary:disabled,.btn-primary:disabled:hover,.btn-primary.disabled,.btn-primary.disabled:hover{color:#fefefe;background-color:#add39f;background-image:-webkit-linear-gradient(#c3ecb4, #add39f);background-image:linear-gradient(#c3ecb4, #add39f);border-color:#b9dcac #b9dcac #a7c89b}.btn-danger{color:#900}.btn-danger:hover{color:#fff;background-color:#b33630;background-image:-webkit-linear-gradient(#dc5f59, #b33630);background-image:linear-gradient(#dc5f59, #b33630);border-color:#cd504a}.btn-danger:active,.btn-danger.selected{color:#fff;background-color:#b33630;background-image:none;border-color:#9f312c}.btn-danger.selected:hover{background-color:#9f302b}.btn-danger:disabled,.btn-danger:disabled:hover,.btn-danger.disabled,.btn-danger.disabled:hover{color:#cb7f7f;background-color:#efefef;background-image:-webkit-linear-gradient(#fefefe, #efefef);background-image:linear-gradient(#fefefe, #efefef);border-color:#e1e1e1}.btn-danger:hover .counter,.btn-danger:active .counter,.btn-danger.selected .counter{color:#b33630;background-color:#fff}.btn-outline{color:#4078c0;background-color:#fff;background-image:none;border:1px solid #e5e5e5}.btn-outline .counter{background-color:#eee}.btn-outline:hover,.btn-outline:active,.btn-outline.selected,.btn-outline.zeroclipboard-is-hover,.btn-outline.zeroclipboard-is-active{color:#fff;background-color:#4078c0;background-image:none;border-color:#4078c0}.btn-outline:hover .counter,.btn-outline:active .counter,.btn-outline.selected .counter,.btn-outline.zeroclipboard-is-hover .counter,.btn-outline.zeroclipboard-is-active .counter{color:#4078c0;background-color:#fff}.btn-outline.selected:hover{background-color:#396cad}.btn-outline:disabled,.btn-outline:disabled:hover,.btn-outline.disabled,.btn-outline.disabled:hover{color:#767676;background-color:#fff;background-image:none;border-color:#e5e5e5}.btn-with-count{float:left;border-top-right-radius:0;border-bottom-right-radius:0}.btn-sm{padding:3px 10px;font-size:12px;line-height:20px}.hidden-text-expander{display:block}.hidden-text-expander.inline{position:relative;top:-1px;display:inline-block;margin-left:5px;line-height:0}.hidden-text-expander a,.ellipsis-expander{display:inline-block;height:12px;padding:0 5px 5px;font-size:12px;font-weight:bold;line-height:6px;color:#555;text-decoration:none;vertical-align:middle;background:#ddd;border:0;border-radius:1px}.hidden-text-expander a:hover,.ellipsis-expander:hover{text-decoration:none;background-color:#ccc}.hidden-text-expander a:active,.ellipsis-expander:active{color:#fff;background-color:#4183c4}.social-count{float:left;padding:3px 7px;font-size:12px;font-weight:600;line-height:20px;color:#333;vertical-align:middle;background-color:#fff;border:1px solid #ddd;border-left:0;border-top-right-radius:3px;border-bottom-right-radius:3px}.social-count:hover,.social-count:active{text-decoration:none}.social-count:hover{color:#4078c0;cursor:pointer}.btn-block{display:block;width:100%;text-align:center}.btn-link{display:inline-block;padding:0;font-size:inherit;color:#4078c0;white-space:nowrap;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background-color:transparent;border:0;-webkit-appearance:none;-moz-appearance:none;appearance:none}.btn-link:hover,.btn-link:focus{text-decoration:underline}.btn-link:focus{outline:none}.btn-group{display:inline-block;vertical-align:middle}.btn-group::before{display:table;content:""}.btn-group::after{display:table;clear:both;content:""}.btn-group .btn{position:relative;float:left}.btn-group .btn:not(:first-child):not(:last-child){border-radius:0}.btn-group .btn:first-child:not(:last-child){border-top-right-radius:0;border-bottom-right-radius:0}.btn-group .btn:last-child:not(:first-child){border-top-left-radius:0;border-bottom-left-radius:0}.btn-group .btn:hover,.btn-group .btn:active,.btn-group .btn.selected{z-index:2}.btn-group .btn:focus{z-index:3}.btn-group .btn+.btn{margin-left:-1px}.btn-group .btn+.btn-group-form,.btn-group .btn-group-form+.btn,.btn-group .btn-group-form+.btn-group-form{margin-left:-1px}.btn-group .btn-group-form{float:left}.btn-group .btn-group-form .btn{border-radius:0}.btn-group .btn-group-form:first-child .btn{border-top-left-radius:3px;border-bottom-left-radius:3px}.btn-group .btn-group-form:last-child .btn{border-top-right-radius:3px;border-bottom-right-radius:3px}.btn-group+.btn-group,.btn-group+.btn{margin-left:5px}.flex-table{display:table}.flex-table-item{display:table-cell;width:1%;white-space:nowrap;vertical-align:middle}.flex-table-item-primary{width:99%}input,textarea{-webkit-font-feature-settings:"liga" 0;font-feature-settings:"liga" 0}fieldset{padding:0;margin:0;border:0}label{font-weight:600}.form-control,.form-select{min-height:34px;padding:6px 8px;font-size:14px;line-height:20px;color:#333;vertical-align:middle;background-color:#fff;background-repeat:no-repeat;background-position:right 8px center;border:1px solid #ddd;border-radius:3px;outline:none;box-shadow:inset 0 1px 2px rgba(0,0,0,0.075)}.form-control.focus,.form-control:focus,.form-select.focus,.form-select:focus{border-color:#51a7e8;outline:none;box-shadow:inset 0 1px 2px rgba(0,0,0,0.075),0 0 5px rgba(81,167,232,0.5)}.input-contrast{background-color:#fafafa}.input-contrast:focus{background-color:#fff}::-webkit-input-placeholder{color:#999}::-moz-placeholder{color:#999}:-ms-input-placeholder{color:#999}::placeholder{color:#999}.input-sm{min-height:28px;padding-top:3px;padding-bottom:3px;font-size:12px;line-height:20px}.input-lg{padding:6px 10px;font-size:16px}.input-block{display:block;width:100%}.input-monospace{font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace}.form-checkbox{padding-left:20px;margin:15px 0;vertical-align:middle}.form-checkbox label em.highlight{position:relative;left:-4px;padding:2px 4px;font-style:normal;background:#fffbdc;border-radius:3px}.form-checkbox input[type=checkbox],.form-checkbox input[type=radio]{float:left;margin:2px 0 0 -20px;vertical-align:middle}.form-checkbox .note{display:block;margin:0;font-size:12px;font-weight:normal;color:#666}.hfields{margin:15px 0}.hfields::before{display:table;content:""}.hfields::after{display:table;clear:both;content:""}.hfields .form-group{float:left;margin:0 30px 0 0}.hfields .form-group dt label{display:inline-block;margin:5px 0 0;color:#666}.hfields .form-group dt img{position:relative;top:-2px}.hfields .btn{float:left;margin:28px 25px 0 -20px}.hfields .form-select{margin-top:5px}input::-webkit-outer-spin-button,input::-webkit-inner-spin-button{margin:0;-webkit-appearance:none;appearance:none}.form-actions::before{display:table;content:""}.form-actions::after{display:table;clear:both;content:""}.form-actions .btn{float:right}.form-actions .btn+.btn{margin-right:5px}.form-warning{padding:8px 10px;margin:10px 0;font-size:14px;color:#4c4a42;background:#fff9ea;border:1px solid #dfd8c2;border-radius:3px}.form-warning p{margin:0;line-height:1.5}.form-warning a{font-weight:bold}.form-select{display:inline-block;max-width:100%;height:34px;padding-right:24px;padding-right:8px \9;background:#fff url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAABAAAAAUCAMAAACzvE1FAAAADFBMVEUzMzMzMzMzMzMzMzMKAG/3AAAAA3RSTlMAf4C/aSLHAAAAPElEQVR42q3NMQ4AIAgEQTn//2cLdRKppSGzBYwzVXvznNWs8C58CiussPJj8h6NwgorrKRdTvuV9v16Afn0AYFOB7aYAAAAAElFTkSuQmCC") no-repeat right 8px center;background-image:none \9;background-size:8px 10px;-webkit-appearance:none;-moz-appearance:none;appearance:none}.form-select::-ms-expand{opacity:0}.form-select[multiple]{height:auto}.select-sm{height:28px;min-height:28px;padding-top:3px;padding-bottom:3px;font-size:12px}.select-sm[multiple]{height:auto;min-height:0}.form-group{margin:15px 0}.form-group .form-control{width:440px;max-width:100%;margin-right:5px;background-color:#fafafa}.form-group .form-control:focus{background-color:#fff}.form-group .form-control.shorter{width:130px}.form-group .form-control.short{width:250px}.form-group .form-control.long{width:100%}.form-group textarea.form-control{width:100%;height:200px;min-height:200px}.form-group textarea.form-control.short{height:50px;min-height:50px}.form-group dt{margin:0 0 6px}.form-group label{position:relative}.form-group.flattened dt{float:left;margin:0;line-height:32px}.form-group.flattened dd{line-height:32px}.form-group dd h4{margin:4px 0 0}.form-group dd h4.is-error{color:#bd2c00}.form-group dd h4.is-success{color:#55a532}.form-group dd h4+.note{margin-top:0}.form-group.required dt label::after{padding-left:5px;color:#bd2c00;content:"*"}.form-group .success,.form-group .error,.form-group .indicator{display:none;font-size:12px;font-weight:bold}.form-group.loading{opacity:0.5}.form-group.loading .indicator{display:inline}.form-group.loading .spinner{display:inline-block;vertical-align:middle}.form-group.successful .success{display:inline;color:#55a532}.form-group.warn .warning,.form-group.warn .error,.form-group.errored .warning,.form-group.errored .error{position:absolute;z-index:10;display:inline-block;max-width:450px;padding:5px 8px;margin:3px 0 0;font-size:13px;font-weight:normal;border-style:solid;border-width:1px;border-radius:3px}.form-group.warn .warning::after,.form-group.warn .warning::before,.form-group.warn .error::after,.form-group.warn .error::before,.form-group.errored .warning::after,.form-group.errored .warning::before,.form-group.errored .error::after,.form-group.errored .error::before{position:absolute;bottom:100%;left:10px;z-index:15;width:0;height:0;pointer-events:none;content:" ";border:solid transparent}.form-group.warn .warning::after,.form-group.warn .error::after,.form-group.errored .warning::after,.form-group.errored .error::after{border-width:5px}.form-group.warn .warning::before,.form-group.warn .error::before,.form-group.errored .warning::before,.form-group.errored .error::before{margin-left:-1px;border-width:6px}.form-group.warn .warning{color:#4c4a42;background-color:#fff9ea;border-color:#dfd8c2}.form-group.warn .warning::after{border-bottom-color:#fff9ea}.form-group.warn .warning::before{border-bottom-color:#dfd8c2}.form-group.errored label{color:#bd2c00}.form-group.errored .error{color:#911;background-color:#fcdede;border-color:#d2b2b2}.form-group.errored .error::after{border-bottom-color:#fcdede}.form-group.errored .error::before{border-bottom-color:#d2b2b2}.note{min-height:17px;margin:4px 0 2px;font-size:12px;color:#767676}.note .spinner{margin-right:3px;vertical-align:middle}dl.form-group>dd .form-control.is-autocheck-loading,dl.form-group>dd .form-control.is-autocheck-successful,dl.form-group>dd .form-control.is-autocheck-errored{padding-right:30px}dl.form-group>dd .form-control.is-autocheck-loading{background-image:url("/images/spinners/octocat-spinner-16px.gif")}dl.form-group>dd .form-control.is-autocheck-successful{background-image:url("/images/modules/ajax/success.png")}dl.form-group>dd .form-control.is-autocheck-errored{background-image:url("/images/modules/ajax/error.png")}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){dl.form-group>dd .form-control.is-autocheck-loading,dl.form-group>dd .form-control.is-autocheck-successful,dl.form-group>dd .form-control.is-autocheck-errored{background-size:16px 16px}dl.form-group>dd .form-control.is-autocheck-loading{background-image:url("/images/spinners/octocat-spinner-32.gif")}dl.form-group>dd .form-control.is-autocheck-successful{background-image:url("/images/modules/ajax/success@2x.png")}dl.form-group>dd .form-control.is-autocheck-errored{background-image:url("/images/modules/ajax/error@2x.png")}}.form-cards{height:31px;margin:0 0 15px}.form-cards .card{float:left;width:47px;height:31px;text-indent:-9999px;background-image:url("/images/modules/pricing/credit-cards-@1x.png");background-position:0 0;opacity:0.6}.form-cards .card.visa{background-position:0 0}.form-cards .card.amex{background-position:-50px 0}.form-cards .card.mastercard{background-position:-100px 0}.form-cards .card.discover{background-position:-150px 0}.form-cards .card.jcb{background-position:-200px 0}.form-cards .card.dinersclub{background-position:-250px 0}.form-cards .card.enabled{opacity:1}.form-cards .card.disabled{opacity:0.2}.form-cards>.cards{margin:0}.form-cards>.cards>li{float:left;margin:0 4px 0 0;list-style-type:none}.form-cards>.cards>li.text{line-height:31px}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){.form-cards>.cards .card{background-image:url("/images/modules/pricing/credit-cards-@2x.png");background-size:300px 31px}}.status-indicator{display:inline-block;width:16px;height:16px;margin-left:5px;vertical-align:text-bottom}.status-indicator .octicon{display:none}.status-indicator-success{width:12px;margin-left:7px}.status-indicator-success::before{content:""}.status-indicator-success .octicon-check{display:inline-block;color:#6cc644;fill:#6cc644}.status-indicator-success .octicon-x{display:none}.status-indicator-failed{margin-left:7px}.status-indicator-failed::before{content:""}.status-indicator-failed .octicon-check{display:none}.status-indicator-failed .octicon-x{display:inline-block;color:#bd2c00;fill:#bd2c00}.status-indicator-loading{width:16px;background:url("/images/spinners/octocat-spinner-32-EAF2F5.gif") 0 0 no-repeat;background-size:16px}.inline-form{display:inline-block}.inline-form .btn-plain{background-color:transparent;border:0}.drag-and-drop{padding:7px 10px;margin:0;font-size:13px;line-height:16px;color:#767676;background-color:#fafafa;border:1px solid #ccc;border-top:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.drag-and-drop .default,.drag-and-drop .loading,.drag-and-drop .error{display:none}.drag-and-drop .error{color:#bd2c00}.drag-and-drop img{vertical-align:top}.is-default .drag-and-drop .default{display:inline-block}.is-uploading .drag-and-drop .loading{display:inline-block}.is-bad-file .drag-and-drop .bad-file{display:inline-block}.is-duplicate-filename .drag-and-drop .duplicate-filename{display:inline-block}.is-too-big .drag-and-drop .too-big{display:inline-block}.is-hidden-file .drag-and-drop .hidden-file{display:inline-block}.is-empty .drag-and-drop .empty{display:inline-block}.is-bad-permissions .drag-and-drop .bad-permissions{display:inline-block}.is-repository-required .drag-and-drop .repository-required{display:inline-block}.drag-and-drop-error-info{font-weight:normal;color:#767676}.drag-and-drop-error-info a{color:#4078c0}.is-failed .drag-and-drop .failed-request{display:inline-block}.manual-file-chooser{position:absolute;width:240px;padding:5px;margin-left:-80px;cursor:pointer;opacity:0.0001}.manual-file-chooser:hover+.manual-file-chooser-text{text-decoration:underline}.btn .manual-file-chooser{top:0;padding:0;line-height:34px}.upload-enabled textarea{display:block;border-bottom:1px dashed #ddd;border-bottom-right-radius:0;border-bottom-left-radius:0}.focused .drag-and-drop{border-color:#51a7e8;box-shadow:rgba(81,167,232,0.5) 0 0 3px}.dragover textarea,.dragover .drag-and-drop{box-shadow:#c9ff00 0 0 3px}.write-content{position:relative}.previewable-comment-form{position:relative}.previewable-comment-form .tabnav{position:relative;padding:10px 10px 0}.previewable-comment-form .comment{border:1px solid #cacaca}.previewable-comment-form .comment-form-error{margin-bottom:10px}.previewable-comment-form .write-content,.previewable-comment-form .preview-content{display:none;padding:0 0 10px;margin:0 10px}.previewable-comment-form.write-selected .write-content,.previewable-comment-form.preview-selected .preview-content{display:block}.previewable-comment-form textarea{display:block;width:100%;min-height:100px;max-height:500px;padding:10px;resize:vertical}.form-action-spacious{margin-top:10px}div.composer{margin-top:0;border:0}.composer .comment-form-textarea{height:200px;min-height:200px}.composer .tabnav{margin:0 0 10px}h2.account{margin:15px 0 0;font-size:18px;font-weight:normal;color:#666}p.explain{position:relative;font-size:12px;color:#666}p.explain strong{color:#333}p.explain .octicon{margin-right:5px;color:#bbb}p.explain .minibutton{top:-4px;float:right}.form-group label{position:static}.container{width:980px;margin-right:auto;margin-left:auto}.container::before{display:table;content:""}.container::after{display:table;clear:both;content:""}.columns{margin-right:-10px;margin-left:-10px}.columns::before{display:table;content:""}.columns::after{display:table;clear:both;content:""}.column{float:left;padding-right:10px;padding-left:10px}.one-third{width:33.333333%}.two-thirds{width:66.666667%}.one-fourth{width:25%}.one-half{width:50%}.three-fourths{width:75%}.one-fifth{width:20%}.four-fifths{width:80%}.single-column{padding-right:10px;padding-left:10px}.table-column{display:table-cell;width:1%;padding-right:10px;padding-left:10px;vertical-align:top}.centered{display:block;float:none;margin-right:auto;margin-left:auto}.markdown-body{font-family:-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";font-size:16px;line-height:1.5;word-wrap:break-word;}.markdown-body::before{display:table;content:"";}.markdown-body::after{display:table;clear:both;content:"";}.markdown-body>*:first-child{margin-top:0 !important}.markdown-body>*:last-child{margin-bottom:0 !important}.markdown-body a:not([href]){color:inherit;text-decoration:none}.markdown-body .absent{color:#c00}.markdown-body .anchor{display:inline-block;padding-right:2px;margin-left:-18px}.markdown-body .anchor:focus{outline:none}.markdown-body p,.markdown-body blockquote,.markdown-body ul,.markdown-body ol,.markdown-body dl,.markdown-body table,.markdown-body pre{margin-top:0;margin-bottom:16px}.markdown-body hr{height:4px;padding:0;margin:16px 0;background-color:#e7e7e7;border:0 none}.markdown-body blockquote{padding:0 15px;color:#777;border-left:4px solid #ddd}.markdown-body blockquote>:first-child{margin-top:0}.markdown-body blockquote>:last-child{margin-bottom:0}.markdown-body kbd{display:inline-block;padding:3px 5px;font-size:11px;line-height:10px;color:#555;vertical-align:middle;background-color:#fcfcfc;border:solid 1px #ccc;border-bottom-color:#bbb;border-radius:3px;box-shadow:inset 0 -1px 0 #bbb}.markdown-body h1,.markdown-body h2,.markdown-body h3,.markdown-body h4,.markdown-body h5,.markdown-body h6{margin-top:1em;margin-bottom:16px;font-weight:bold;line-height:1.4}.markdown-body h1 .octicon-link,.markdown-body h2 .octicon-link,.markdown-body h3 .octicon-link,.markdown-body h4 .octicon-link,.markdown-body h5 .octicon-link,.markdown-body h6 .octicon-link{color:#000;vertical-align:middle;visibility:hidden}.markdown-body h1:hover .anchor,.markdown-body h2:hover .anchor,.markdown-body h3:hover .anchor,.markdown-body h4:hover .anchor,.markdown-body h5:hover .anchor,.markdown-body h6:hover .anchor{text-decoration:none}.markdown-body h1:hover .anchor .octicon-link,.markdown-body h2:hover .anchor .octicon-link,.markdown-body h3:hover .anchor .octicon-link,.markdown-body h4:hover .anchor .octicon-link,.markdown-body h5:hover .anchor .octicon-link,.markdown-body h6:hover .anchor .octicon-link{visibility:visible}.markdown-body h1 tt,.markdown-body h1 code,.markdown-body h2 tt,.markdown-body h2 code,.markdown-body h3 tt,.markdown-body h3 code,.markdown-body h4 tt,.markdown-body h4 code,.markdown-body h5 tt,.markdown-body h5 code,.markdown-body h6 tt,.markdown-body h6 code{font-size:inherit}.markdown-body h1{padding-bottom:0.3em;font-size:2.25em;line-height:1.2;border-bottom:1px solid #eee}.markdown-body h1 .anchor{line-height:1}.markdown-body h2{padding-bottom:0.3em;font-size:1.75em;line-height:1.225;border-bottom:1px solid #eee}.markdown-body h2 .anchor{line-height:1}.markdown-body h3{font-size:1.5em;line-height:1.43}.markdown-body h3 .anchor{line-height:1.2}.markdown-body h4{font-size:1.25em}.markdown-body h4 .anchor{line-height:1.2}.markdown-body h5{font-size:1em}.markdown-body h5 .anchor{line-height:1.1}.markdown-body h6{font-size:1em;color:#777}.markdown-body h6 .anchor{line-height:1.1}.markdown-body ul,.markdown-body ol{padding-left:2em}.markdown-body ul.no-list,.markdown-body ol.no-list{padding:0;list-style-type:none}.markdown-body ul ul,.markdown-body ul ol,.markdown-body ol ol,.markdown-body ol ul{margin-top:0;margin-bottom:0}.markdown-body li>p{margin-top:16px}.markdown-body li+li{margin-top:0.25em}.markdown-body dl{padding:0}.markdown-body dl dt{padding:0;margin-top:16px;font-size:1em;font-style:italic;font-weight:bold}.markdown-body dl dd{padding:0 16px;margin-bottom:16px}.markdown-body table{display:block;width:100%;overflow:auto;word-break:normal;word-break:keep-all;}.markdown-body table th{font-weight:bold}.markdown-body table th,.markdown-body table td{padding:6px 13px;border:1px solid #ddd}.markdown-body table tr{background-color:#fff;border-top:1px solid #ccc}.markdown-body table tr:nth-child(2n){background-color:#f8f8f8}.markdown-body img{max-width:100%;box-sizing:content-box;background-color:#fff}.markdown-body img[align=right]{padding-left:20px}.markdown-body img[align=left]{padding-right:20px}.markdown-body .emoji{max-width:none;vertical-align:text-top;background-color:transparent}.markdown-body span.frame{display:block;overflow:hidden}.markdown-body span.frame>span{display:block;float:left;width:auto;padding:7px;margin:13px 0 0;overflow:hidden;border:1px solid #ddd}.markdown-body span.frame span img{display:block;float:left}.markdown-body span.frame span span{display:block;padding:5px 0 0;clear:both;color:#333}.markdown-body span.align-center{display:block;overflow:hidden;clear:both}.markdown-body span.align-center>span{display:block;margin:13px auto 0;overflow:hidden;text-align:center}.markdown-body span.align-center span img{margin:0 auto;text-align:center}.markdown-body span.align-right{display:block;overflow:hidden;clear:both}.markdown-body span.align-right>span{display:block;margin:13px 0 0;overflow:hidden;text-align:right}.markdown-body span.align-right span img{margin:0;text-align:right}.markdown-body span.float-left{display:block;float:left;margin-right:13px;overflow:hidden}.markdown-body span.float-left span{margin:13px 0 0}.markdown-body span.float-right{display:block;float:right;margin-left:13px;overflow:hidden}.markdown-body span.float-right>span{display:block;margin:13px auto 0;overflow:hidden;text-align:right}.markdown-body code,.markdown-body tt{padding:0;padding-top:0.2em;padding-bottom:0.2em;margin:0;font-size:85%;background-color:rgba(0,0,0,0.04);border-radius:3px}.markdown-body code::before,.markdown-body code::after,.markdown-body tt::before,.markdown-body tt::after{letter-spacing:-0.2em;content:"\00a0"}.markdown-body code br,.markdown-body tt br{display:none}.markdown-body del code{text-decoration:inherit}.markdown-body pre{word-wrap:normal}.markdown-body pre>code{padding:0;margin:0;font-size:100%;word-break:normal;white-space:pre;background:transparent;border:0}.markdown-body .highlight{margin-bottom:16px}.markdown-body .highlight pre{margin-bottom:0;word-break:normal}.markdown-body .highlight pre,.markdown-body pre{padding:16px;overflow:auto;font-size:85%;line-height:1.45;background-color:#f7f7f7;border-radius:3px}.markdown-body pre code,.markdown-body pre tt{display:inline;max-width:auto;padding:0;margin:0;overflow:visible;line-height:inherit;word-wrap:normal;background-color:transparent;border:0}.markdown-body pre code::before,.markdown-body pre code::after,.markdown-body pre tt::before,.markdown-body pre tt::after{content:normal}.counter{display:inline-block;padding:2px 5px;font-size:11px;font-weight:bold;line-height:1;color:#666;background-color:#eee;border-radius:20px}.menu{margin-bottom:15px;list-style:none;background-color:#fff;border:1px solid #d8d8d8;border-radius:3px}.menu-item{position:relative;display:block;padding:8px 10px;text-shadow:0 1px 0 #fff;border-bottom:1px solid #eee}.menu-item:first-child{border-top:0;border-top-left-radius:2px;border-top-right-radius:2px}.menu-item:first-child::before{border-top-left-radius:2px}.menu-item:last-child{border-bottom:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px}.menu-item:last-child::before{border-bottom-left-radius:2px}.menu-item:hover{text-decoration:none;background-color:#f9f9f9}.menu-item.selected{font-weight:bold;color:#222;cursor:default;background-color:#fff}.menu-item.selected::before{position:absolute;top:0;bottom:0;left:0;width:2px;content:"";background-color:#d26911}.menu-item .octicon{width:16px;margin-right:5px;color:#333;text-align:center}.menu-item .counter{float:right;margin-left:5px}.menu-item .menu-warning{float:right;color:#d26911}.menu-item .avatar{float:left;margin-right:5px}.menu-item.alert .counter{color:#bd2c00}.menu-heading{display:block;padding:8px 10px;margin-top:0;margin-bottom:0;font-size:13px;font-weight:bold;line-height:20px;color:#555;background-color:#f7f7f7;border-bottom:1px solid #eee}.menu-heading:hover{text-decoration:none}.menu-heading:first-child{border-top-left-radius:2px;border-top-right-radius:2px}.menu-heading:last-child{border-bottom:0;border-bottom-right-radius:2px;border-bottom-left-radius:2px}.tabnav{margin-top:0;margin-bottom:15px;border-bottom:1px solid #ddd}.tabnav .counter{margin-left:5px}.tabnav-tabs{margin-bottom:-1px}.tabnav-tab{display:inline-block;padding:8px 12px;font-size:14px;line-height:20px;color:#666;text-decoration:none;background-color:transparent;border:1px solid transparent;border-bottom:0}.tabnav-tab.selected{color:#333;background-color:#fff;border-color:#ddd;border-radius:3px 3px 0 0}.tabnav-tab:hover,.tabnav-tab:focus{text-decoration:none}.tabnav-extra{display:inline-block;padding-top:10px;margin-left:10px;font-size:12px;color:#666}.tabnav-extra>.octicon{margin-right:2px}a.tabnav-extra:hover{color:#4078c0;text-decoration:none}.tabnav-btn{margin-left:10px}.filter-list{list-style-type:none}.filter-list.small .filter-item{padding:4px 10px;margin:0 0 2px;font-size:12px}.filter-list.pjax-active .filter-item{color:#767676;background-color:transparent}.filter-list.pjax-active .filter-item.pjax-active{color:#fff;background-color:#4078c0}.filter-item{position:relative;display:block;padding:8px 10px;margin-bottom:5px;overflow:hidden;font-size:14px;color:#767676;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;cursor:pointer;border-radius:3px}.filter-item:hover{text-decoration:none;background-color:#eee}.filter-item.selected{color:#fff;background-color:#4078c0}.filter-item .count{float:right;font-weight:bold}.filter-item .bar{position:absolute;top:2px;right:0;bottom:2px;z-index:-1;display:inline-block;background-color:#f1f1f1}.subnav{margin-bottom:20px}.subnav::before{display:table;content:""}.subnav::after{display:table;clear:both;content:""}.subnav>.right{margin-left:10px}.subnav-bordered{padding-bottom:20px;border-bottom:1px solid #eee}.subnav-flush{margin-bottom:0}.subnav-item{position:relative;float:left;padding:7px 14px;font-weight:bold;color:#666;border:1px solid #e5e5e5}.subnav-item+.subnav-item{margin-left:-1px}.subnav-item:hover,.subnav-item:focus{text-decoration:none;background-color:#f5f5f5}.subnav-item.selected,.subnav-item.selected:hover,.subnav-item.selected:focus{z-index:2;color:#fff;background-color:#4078c0;border-color:#4078c0}.subnav-item:first-child{border-top-left-radius:3px;border-bottom-left-radius:3px}.subnav-item:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.subnav-search{position:relative;margin-left:10px}.subnav-search-input{width:320px;padding-left:30px;color:#767676;border-color:#d5d5d5}.subnav-search-input-wide{width:500px}.subnav-search-icon{position:absolute;top:9px;left:8px;display:block;color:#ccc;text-align:center;pointer-events:none}.subnav-search-context .btn{color:#555;border-top-right-radius:0;border-bottom-right-radius:0}.subnav-search-context .btn:hover,.subnav-search-context .btn:focus,.subnav-search-context .btn:active,.subnav-search-context .btn.selected{z-index:2}.subnav-search-context+.subnav-search{margin-left:-1px}.subnav-search-context+.subnav-search .subnav-search-input{border-top-left-radius:0;border-bottom-left-radius:0}.subnav-search-context .select-menu-modal-holder{z-index:30}.subnav-search-context .select-menu-modal{width:220px}.subnav-search-context .select-menu-item-icon{color:inherit}.subnav-spacer-right{padding-right:10px}.state{display:inline-block;padding:4px 8px;font-weight:bold;line-height:20px;color:#fff;text-align:center;background-color:#999;border-radius:3px}.state-open,.state-proposed,.state-reopened{background-color:#6cc644}.state-merged{background-color:#6e5494}.state-closed{background-color:#bd2c00}.state-renamed{background-color:#fffa5d}.tooltipped{position:relative}.tooltipped::after{position:absolute;z-index:1000000;display:none;padding:5px 8px;font:normal normal 11px/1.5 -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji", "Segoe UI Symbol";-webkit-font-smoothing:subpixel-antialiased;color:#fff;text-align:center;text-decoration:none;text-shadow:none;text-transform:none;letter-spacing:normal;word-wrap:break-word;white-space:pre;pointer-events:none;content:attr(aria-label);background:rgba(0,0,0,0.8);border-radius:3px;opacity:0}.tooltipped::before{position:absolute;z-index:1000001;display:none;width:0;height:0;color:rgba(0,0,0,0.8);pointer-events:none;content:"";border:5px solid transparent;opacity:0}@-webkit-keyframes tooltip-appear{from{opacity:0}to{opacity:1}}@keyframes tooltip-appear{from{opacity:0}to{opacity:1}}.tooltipped:hover::before,.tooltipped:hover::after,.tooltipped:active::before,.tooltipped:active::after,.tooltipped:focus::before,.tooltipped:focus::after{display:inline-block;text-decoration:none;-webkit-animation-name:tooltip-appear;animation-name:tooltip-appear;-webkit-animation-duration:0.1s;animation-duration:0.1s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in;-webkit-animation-delay:0.4s;animation-delay:0.4s}.tooltipped-no-delay:hover::before,.tooltipped-no-delay:hover::after,.tooltipped-no-delay:active::before,.tooltipped-no-delay:active::after,.tooltipped-no-delay:focus::before,.tooltipped-no-delay:focus::after{opacity:1;-webkit-animation:none;animation:none}.tooltipped-multiline:hover::after,.tooltipped-multiline:active::after,.tooltipped-multiline:focus::after{display:table-cell}.tooltipped-s::after,.tooltipped-se::after,.tooltipped-sw::after{top:100%;right:50%;margin-top:5px}.tooltipped-s::before,.tooltipped-se::before,.tooltipped-sw::before{top:auto;right:50%;bottom:-5px;margin-right:-5px;border-bottom-color:rgba(0,0,0,0.8)}.tooltipped-se::after{right:auto;left:50%;margin-left:-15px}.tooltipped-sw::after{margin-right:-15px}.tooltipped-n::after,.tooltipped-ne::after,.tooltipped-nw::after{right:50%;bottom:100%;margin-bottom:5px}.tooltipped-n::before,.tooltipped-ne::before,.tooltipped-nw::before{top:-5px;right:50%;bottom:auto;margin-right:-5px;border-top-color:rgba(0,0,0,0.8)}.tooltipped-ne::after{right:auto;left:50%;margin-left:-15px}.tooltipped-nw::after{margin-right:-15px}.tooltipped-s::after,.tooltipped-n::after{-webkit-transform:translateX(50%);transform:translateX(50%)}.tooltipped-w::after{right:100%;bottom:50%;margin-right:5px;-webkit-transform:translateY(50%);transform:translateY(50%)}.tooltipped-w::before{top:50%;bottom:50%;left:-5px;margin-top:-5px;border-left-color:rgba(0,0,0,0.8)}.tooltipped-e::after{bottom:50%;left:100%;margin-left:5px;-webkit-transform:translateY(50%);transform:translateY(50%)}.tooltipped-e::before{top:50%;right:-5px;bottom:50%;margin-top:-5px;border-right-color:rgba(0,0,0,0.8)}.tooltipped-multiline::after{width:-webkit-max-content;width:-moz-max-content;width:max-content;max-width:250px;word-break:break-word;word-wrap:normal;white-space:pre-line;border-collapse:separate}.tooltipped-multiline.tooltipped-s::after,.tooltipped-multiline.tooltipped-n::after{right:auto;left:50%;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.tooltipped-multiline.tooltipped-w::after,.tooltipped-multiline.tooltipped-e::after{right:100%}@media screen and (min-width: 0\0){.tooltipped-multiline::after{width:250px}}.tooltipped-sticky::before,.tooltipped-sticky::after{display:inline-block}.tooltipped-sticky.tooltipped-multiline::after{display:table-cell}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){.tooltipped-w::after{margin-right:4.5px}}.css-truncate.css-truncate-target,.css-truncate .css-truncate-target{display:inline-block;max-width:125px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;vertical-align:top}.css-truncate.expandable.zeroclipboard-is-hover .css-truncate-target,.css-truncate.expandable.zeroclipboard-is-hover.css-truncate-target,.css-truncate.expandable:hover .css-truncate-target,.css-truncate.expandable:hover.css-truncate-target{max-width:10000px !important}.border{border:1px #e5e5e5 solid !important}.border-top{border-top:1px #e5e5e5 solid !important}.border-right{border-right:1px #e5e5e5 solid !important}.border-bottom{border-bottom:1px #e5e5e5 solid !important}.border-left{border-left:1px #e5e5e5 solid !important}.border-y{border-top:1px #e5e5e5 solid !important;border-bottom:1px #e5e5e5 solid !important}.border-blue{border-color:#c5d5dd !important}.border-gray-light{border-color:#eee !important}.border-gray-dark{border-color:#ddd !important}.border-0{border:0 !important}.border-top-0{border-top:0 !important}.border-right-0{border-right:0 !important}.border-bottom-0{border-bottom:0 !important}.border-left-0{border-left:0 !important}.rounded-0{border-radius:0 !important}.rounded-1{border-radius:3px !important}.rounded-2{border-radius:6px !important}.bg-white{background-color:#fff !important}.bg-blue{background-color:#4078c0 !important}.bg-blue-light{background-color:#f2f8fa !important}.bg-gray-dark{background-color:#333 !important}.bg-gray{background-color:#f5f5f5 !important}.bg-gray-light{background-color:#fafafa !important}.bg-green{background-color:#6cc644 !important}.bg-red{background-color:#bd2c00 !important}.text-blue{color:#4078c0 !important}.danger,.text-closed,.text-danger,.text-diff-deleted,.text-error,.text-failure,.text-red{color:#bd2c00 !important}.text-gray-light{color:#999 !important}.text-muted,.text-gray{color:#767676 !important}.mute,.text-gray-dark{color:#333 !important}.text-diff-added,.text-success,.text-open,.text-green{color:#55a532 !important}.text-orange{color:#c9510c !important}.text-purple{color:#6e5494 !important}.text-white{color:#fff !important}.text-inherit{color:inherit !important}.link-blue{color:#4078c0 !important}.link-gray-dark{color:#333 !important}.link-gray{color:#767676 !important}.text-renamed{color:#fffa5d !important}.text-pending{color:#cea61b !important}.muted-link{color:#767676 !important}.muted-link:hover{color:#4078c0 !important;text-decoration:none}.position-static{position:static !important}.position-relative{position:relative !important}.position-absolute{position:absolute !important}.position-fixed{position:fixed !important}.top-0{top:0 !important}.right-0{right:0 !important}.bottom-0{bottom:0 !important}.left-0{left:0 !important}.v-align-middle{vertical-align:middle !important}.v-align-top{vertical-align:top !important}.v-align-bottom{vertical-align:bottom !important}.v-align-text-top{vertical-align:text-top !important}.v-align-text-bottom{vertical-align:text--bottom !important}.overflow-hidden{overflow:hidden !important}.overflow-scroll{overflow:scroll !important}.overflow-auto{overflow:auto !important}.clearfix::before{display:table;content:""}.clearfix::after{display:table;clear:both;content:""}.right,.float-right{float:right !important}.left,.float-left{float:left !important}.width-fit{max-width:100% !important}.width-full{width:100% !important}.height-full{height:100% !important}.min-width-0{min-width:0 !important}.v-hidden{visibility:hidden !important}.v-visible{visibility:visible !important}.d-table{display:table !important}.d-table-cell{display:table-cell !important}.d-block{display:block !important}.d-inline{display:inline !important}.d-inline-block{display:inline-block !important}.hidden,.d-none{display:none !important}.m-0{margin:0 !important}.mt-0{margin-top:0 !important}.mr-0{margin-right:0 !important}.mb-0{margin-bottom:0 !important}.ml-0{margin-left:0 !important}.mx-0{margin-right:0 !important;margin-left:0 !important}.my-0{margin-top:0 !important;margin-bottom:0 !important}.m-1{margin:3px !important}.mt-1{margin-top:3px !important}.mr-1{margin-right:3px !important}.mb-1{margin-bottom:3px !important}.ml-1{margin-left:3px !important}.mx-1{margin-right:3px !important;margin-left:3px !important}.my-1{margin-top:3px !important;margin-bottom:3px !important}.m-2{margin:6px !important}.mt-2{margin-top:6px !important}.mr-2{margin-right:6px !important}.mb-2{margin-bottom:6px !important}.ml-2{margin-left:6px !important}.mx-2{margin-right:6px !important;margin-left:6px !important}.my-2{margin-top:6px !important;margin-bottom:6px !important}.m-3{margin:12px !important}.mt-3{margin-top:12px !important}.mr-3{margin-right:12px !important}.mb-3{margin-bottom:12px !important}.ml-3{margin-left:12px !important}.mx-3{margin-right:12px !important;margin-left:12px !important}.my-3{margin-top:12px !important;margin-bottom:12px !important}.m-4{margin:24px !important}.mt-4{margin-top:24px !important}.mr-4{margin-right:24px !important}.mb-4{margin-bottom:24px !important}.ml-4{margin-left:24px !important}.mx-4{margin-right:24px !important;margin-left:24px !important}.my-4{margin-top:24px !important;margin-bottom:24px !important}.m-5{margin:36px !important}.mt-5{margin-top:36px !important}.mr-5{margin-right:36px !important}.mb-5{margin-bottom:36px !important}.ml-5{margin-left:36px !important}.mx-5{margin-right:36px !important;margin-left:36px !important}.my-5{margin-top:36px !important;margin-bottom:36px !important}.m-6{margin:48px !important}.mt-6{margin-top:48px !important}.mr-6{margin-right:48px !important}.mb-6{margin-bottom:48px !important}.ml-6{margin-left:48px !important}.mx-6{margin-right:48px !important;margin-left:48px !important}.my-6{margin-top:48px !important;margin-bottom:48px !important}.m-auto{margin-right:auto !important;margin-left:auto !important}.p-0{padding:0 !important}.pt-0{padding-top:0 !important}.pr-0{padding-right:0 !important}.pb-0{padding-bottom:0 !important}.pl-0{padding-left:0 !important}.px-0{padding-right:0 !important;padding-left:0 !important}.py-0{padding-top:0 !important;padding-bottom:0 !important}.p-1{padding:3px !important}.pt-1{padding-top:3px !important}.pr-1{padding-right:3px !important}.pb-1{padding-bottom:3px !important}.pl-1{padding-left:3px !important}.px-1{padding-right:3px !important;padding-left:3px !important}.py-1{padding-top:3px !important;padding-bottom:3px !important}.p-2{padding:6px !important}.pt-2{padding-top:6px !important}.pr-2{padding-right:6px !important}.pb-2{padding-bottom:6px !important}.pl-2{padding-left:6px !important}.px-2{padding-right:6px !important;padding-left:6px !important}.py-2{padding-top:6px !important;padding-bottom:6px !important}.p-3{padding:12px !important}.pt-3{padding-top:12px !important}.pr-3{padding-right:12px !important}.pb-3{padding-bottom:12px !important}.pl-3{padding-left:12px !important}.px-3{padding-right:12px !important;padding-left:12px !important}.py-3{padding-top:12px !important;padding-bottom:12px !important}.p-4{padding:24px !important}.pt-4{padding-top:24px !important}.pr-4{padding-right:24px !important}.pb-4{padding-bottom:24px !important}.pl-4{padding-left:24px !important}.px-4{padding-right:24px !important;padding-left:24px !important}.py-4{padding-top:24px !important;padding-bottom:24px !important}.p-5{padding:36px !important}.pt-5{padding-top:36px !important}.pr-5{padding-right:36px !important}.pb-5{padding-bottom:36px !important}.pl-5{padding-left:36px !important}.px-5{padding-right:36px !important;padding-left:36px !important}.py-5{padding-top:36px !important;padding-bottom:36px !important}.p-6{padding:48px !important}.pt-6{padding-top:48px !important}.pr-6{padding-right:48px !important}.pb-6{padding-bottom:48px !important}.pl-6{padding-left:48px !important}.px-6{padding-right:48px !important;padding-left:48px !important}.py-6{padding-top:48px !important;padding-bottom:48px !important}.h1,.h2,.h3,.h4,.h5,.h6{font-weight:600 !important}.h1{font-size:32px !important}.h2{font-size:24px !important}.h3{font-size:20px !important}.h4{font-size:16px !important}.h5{font-size:14px !important}.h6{font-size:12px !important}.text-small{font-size:12px !important}.lead{margin-bottom:30px;font-size:20px;font-weight:300;color:#555}.lh-condensed-ultra{line-height:1 !important}.lh-condensed{line-height:1.25 !important}.lh-default{line-height:1.5 !important}.text-right{text-align:right !important}.text-left{text-align:left !important}.text-center{text-align:center !important}.text-normal{font-weight:normal !important}.text-bold{font-weight:bold !important}.text-italic{font-style:italic !important}.text-uppercase{text-transform:uppercase !important}.no-underline{text-decoration:none !important}.no-wrap{white-space:nowrap !important}.text-emphasized{font-weight:bold;color:#333}.table-list{display:table;width:100%;color:#999;table-layout:fixed;border-bottom:1px solid #e5e5e5}.table-list ol{list-style-type:decimal}.table-list-bordered .table-list-cell:first-child{border-left:1px solid #eee}.table-list-bordered .table-list-cell:last-child{border-right:1px solid #eee}.table-list-item{position:relative;display:table-row;list-style:none}.table-list-item.unread .table-list-cell:first-child{box-shadow:2px 0 0 #4078c0 inset}.table-list-cell{position:relative;display:table-cell;padding:8px 10px;font-size:12px;vertical-align:top;border-top:1px solid #eee}.table-list-cell.flush-left{padding-left:0}.table-list-cell.flush-right{padding-right:0}.table-list-cell-checkbox{width:30px;padding-right:0;padding-left:0;text-align:center}.table-list-header{position:relative;margin-top:20px;margin-bottom:-1px;background-color:#f8f8f8;border:1px solid #e5e5e5;border-radius:3px 3px 0 0}.table-list-header::before{display:table;content:""}.table-list-header::after{display:table;clear:both;content:""}.table-list-header .btn-link{position:relative;display:inline-block;padding-top:13px;padding-bottom:13px;font-weight:normal}.table-list-heading{margin-left:10px}.table-list-header-select-all{float:left;width:30px;padding:12px 10px;margin-right:5px;margin-left:-1px;text-align:center}.table-list-header-meta{display:inline-block;padding-top:13px;padding-bottom:13px;color:#767676}.table-list-header-toggle h4{padding:12px 0}.table-list-filters:first-child .table-list-header-toggle:first-child{padding-left:12px}.table-list-header-toggle.states .selected{font-weight:bold}.table-list-header-toggle .btn-link{color:#767676}.table-list-header-toggle .btn-link .octicon{margin-right:2px}.table-list-header-toggle .btn-link:hover{color:#222;text-decoration:none}.table-list-header-toggle .btn-link.selected,.table-list-header-toggle .btn-link.selected:hover{color:#222}.table-list-header-toggle .btn-link+.btn-link{margin-left:10px}.table-list-header-toggle .btn-link:disabled,.table-list-header-toggle .btn-link.disabled{pointer-events:none;opacity:0.5}.table-list-header-toggle .select-menu{position:relative}.table-list-header-toggle .select-menu-item.selected{font-weight:bold}.table-list-header-toggle .select-menu-button{padding-right:15px;padding-left:15px}.table-list-header-toggle .select-menu-button:hover,.table-list-header-toggle .select-menu-button.selected,.table-list-header-toggle .select-menu-button.selected:hover{color:#222}.table-list-header-toggle .select-menu-modal-holder{right:10px}.table-list-header-toggle .select-menu-modal-holder .select-menu-modal{margin-top:-1px}.table-list-triage{display:none}.triage-mode .table-list-filters{display:none}.triage-mode .table-list-triage{display:block}.member-list-item .table-list-cell{padding-top:10px;padding-bottom:10px;vertical-align:middle}.member-list-item .table-list-cell-checkbox{width:30px}.member-list-item.adminable .member-info{padding-left:5px}.member-list-item .member-link{display:block;text-decoration:none}.member-list-item .member-link:hover .member-username{color:#4078c0}.member-visibility .octicon{font-size:14px}.member-info{width:280px;padding-left:10px;font-size:14px}.member-info .member-list-avatar{float:left;margin-right:15px}.member-info .member-fullname{max-width:200px;font-weight:normal;color:#767676}.member-info-content{margin-bottom:5px;overflow:hidden}.member-username{display:block;margin-top:4px;color:#333}.member-username .octicon{position:relative;top:-2px;margin-left:2px;font-size:12px;color:#aaa}.member-username.css-truncate-target{display:block}.member-meta{color:#767676;text-align:center}.member-meta .member-meta-link{color:#767676}.member-meta .member-meta-link:hover{color:#4078c0;text-decoration:none}.member-meta .btn-link{color:#767676}.member-meta .btn-link:hover{color:#4078c0;text-decoration:none}.member-meta .select-menu-modal{width:310px}.member-meta .select-menu-modal-holder{right:0;text-align:left}.member-meta .octicon{font-size:14px}.member-follow{text-align:right}.member-selected-actions{display:inline}.member-security .octicon{font-size:14px;color:#c9510c}.member-list-select-all-label{font-weight:normal}.member-list-select-all-label .some-selected{display:none}.member-list-select-all-label.has-selected-members .some-selected{display:inline}.member-list-select-all-label.has-selected-members .none-selected{display:none}.triage-mode .some-selected{display:inline}.triage-mode .none-selected{display:none}.member-toolbar-actions{margin-top:9px;margin-right:9px}.member-action{margin-right:5px}.member-role-select{position:relative;display:inline-block}.member-role-select .select-menu-modal{width:310px}.member-role-menu .select-menu-item-text{padding-right:8px}.anim-fade-in{-webkit-animation-name:fade-in;animation-name:fade-in;-webkit-animation-duration:1s;animation-duration:1s;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out}.anim-fade-in.fast{-webkit-animation-duration:300ms;animation-duration:300ms}@-webkit-keyframes fade-in{0%{opacity:0}100%{opacity:1}}@keyframes fade-in{0%{opacity:0}100%{opacity:1}}.anim-fade-up{opacity:0;-webkit-animation-name:fade-up;animation-name:fade-up;-webkit-animation-duration:0.3s;animation-duration:0.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-timing-function:ease-out;animation-timing-function:ease-out;-webkit-animation-delay:1s;animation-delay:1s}@-webkit-keyframes fade-up{0%{opacity:0.8;-webkit-transform:translateY(100%);transform:translateY(100%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}@keyframes fade-up{0%{opacity:0.8;-webkit-transform:translateY(100%);transform:translateY(100%)}100%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}}.anim-fade-down{-webkit-animation-name:fade-down;animation-name:fade-down;-webkit-animation-duration:0.3s;animation-duration:0.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-timing-function:ease-in;animation-timing-function:ease-in}@-webkit-keyframes fade-down{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0.5;-webkit-transform:translateY(100%);transform:translateY(100%)}}@keyframes fade-down{0%{opacity:1;-webkit-transform:translateY(0);transform:translateY(0)}100%{opacity:0.5;-webkit-transform:translateY(100%);transform:translateY(100%)}}.anim-grow-x{width:0%;-webkit-animation-name:grow-x;animation-name:grow-x;-webkit-animation-duration:0.3s;animation-duration:0.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-timing-function:ease;animation-timing-function:ease;-webkit-animation-delay:0.5s;animation-delay:0.5s}@-webkit-keyframes grow-x{to{width:100%}}@keyframes grow-x{to{width:100%}}.anim-shrink-x{-webkit-animation-name:shrink-x;animation-name:shrink-x;-webkit-animation-duration:0.3s;animation-duration:0.3s;-webkit-animation-fill-mode:forwards;animation-fill-mode:forwards;-webkit-animation-timing-function:ease-in-out;animation-timing-function:ease-in-out;-webkit-animation-delay:0.5s;animation-delay:0.5s}@-webkit-keyframes shrink-x{to{width:0%}}@keyframes shrink-x{to{width:0%}}.anim-scale-in{-webkit-animation-name:scale-in;animation-name:scale-in;-webkit-animation-duration:0.15s;animation-duration:0.15s;-webkit-animation-timing-function:cubic-bezier(0.2, 0, 0.13, 1.5);animation-timing-function:cubic-bezier(0.2, 0, 0.13, 1.5)}@-webkit-keyframes scale-in{0%{opacity:0;-webkit-transform:scale(0.5);transform:scale(0.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}@keyframes scale-in{0%{opacity:0;-webkit-transform:scale(0.5);transform:scale(0.5)}100%{opacity:1;-webkit-transform:scale(1);transform:scale(1)}}.anim-pulse{-webkit-animation-name:pulse;animation-name:pulse;-webkit-animation-duration:2s;animation-duration:2s;-webkit-animation-timing-function:linear;animation-timing-function:linear;-webkit-animation-iteration-count:infinite;animation-iteration-count:infinite}@-webkit-keyframes pulse{0%{opacity:0.3}10%{opacity:1}100%{opacity:0.3}}@keyframes pulse{0%{opacity:0.3}10%{opacity:1}100%{opacity:0.3}}.autocomplete-results{position:absolute;z-index:99;display:none;max-height:20em;overflow-y:auto;font-size:13px;list-style:none;background:#fff;border-radius:3px;box-shadow:0 0 5px rgba(0,0,0,0.3)}.autocomplete-results .no-results{display:none}.autocomplete-group{width:100%;overflow:hidden}.autocomplete-item{display:block;padding:5px;overflow:hidden;font-weight:bold;text-decoration:none;text-overflow:ellipsis;white-space:nowrap;cursor:pointer}.autocomplete-item.selected,.autocomplete-item.navigation-focus{color:#fff;text-decoration:none;background-color:#4078c0}.autocomplete-item.selected .organization-member,.autocomplete-item.selected .ldap-group-dn,.autocomplete-item.navigation-focus .organization-member,.autocomplete-item.navigation-focus .ldap-group-dn{color:#f2f2f2}.autocomplete-item .secondary-label{font-weight:normal}.autocomplete-item .organization-member{float:right;padding-top:1px;color:#808080}.suggester-container{position:absolute;top:0;left:0;z-index:30;-webkit-transform:translateZ(0);transform:translateZ(0)}.suggester{position:relative;top:0;left:0;display:none;min-width:180px;margin-top:20px;background:#fff;border:1px solid #ddd;border-radius:3px;box-shadow:0 0 5px rgba(0,0,0,0.1)}.suggester.active{display:block}.suggester ul{padding:0;margin:0;list-style:none}.suggester li{display:block;padding:5px 10px;font-weight:bold;border-bottom:1px solid #ddd}.suggester li small{font-weight:normal;color:#767676}.suggester li:last-child{border-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.suggester li:first-child a{border-top-left-radius:3px;border-top-right-radius:3px}.suggester li.navigation-focus{color:#fff;text-decoration:none;background:#4078c0}.suggester li.navigation-focus small{color:#fff}.markdown-body .csv-data td,.markdown-body .csv-data th{padding:5px;overflow:hidden;font-size:12px;line-height:1;text-align:left;white-space:nowrap}.markdown-body .csv-data .blob-num{padding:10px 8px 9px;text-align:right;background:#fff;border:0}.markdown-body .csv-data tr{border-top:0}.markdown-body .csv-data th{font-weight:bold;background:#f8f8f8;border-top:0}.too-long-message{display:none;color:#cea61b}.is-too-long-error .too-long-message{display:block}.check-for-fork{display:inline-block}.check-for-fork img{vertical-align:text-bottom}.check-for-fork.is-error .check-for-fork-loading{display:none}.check-for-fork.is-error .check-for-fork-error{display:inline-block}.check-for-fork-error{display:none}.file-commit-form{padding-left:64px}.file-commit-form .commit-form-avatar{float:left;margin-left:-64px;border-radius:4px}.file-commit-form .commit-form{position:relative;padding:15px;margin-bottom:10px;border:1px solid #ddd;border-radius:3px}.file-commit-form .commit-form::after,.file-commit-form .commit-form::before{position:absolute;top:11px;right:100%;left:-16px;display:block;width:0;height:0;pointer-events:none;content:" ";border-color:transparent;border-style:solid solid outset}.file-commit-form .commit-form::after{margin-top:1px;margin-left:2px;border-width:7px;border-right-color:#fff}.file-commit-form .commit-form::before{border-width:8px;border-right-color:#ddd}.file-commit-form .commit-message{min-height:100px}.file-commit-form-heading{margin-bottom:10px}.quick-pull-choice .form-checkbox{padding-left:25px;margin:10px 0}.quick-pull-choice .form-checkbox label{font-weight:normal}.quick-pull-choice .form-checkbox .octicon{width:16px;margin-right:3px;text-align:center}.quick-pull-choice dl.form-group,.quick-pull-choice .form-checkbox:last-child{margin-bottom:0}.quick-pull-choice .quick-pull-branch-name{display:none;padding-left:48px;margin-top:5px}.quick-pull-choice .new-branch-name-input{position:relative;margin-top:5px}.quick-pull-choice .new-branch-name-input input{width:240px;padding-left:26px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace}.quick-pull-choice .new-branch-name-input .quick-pull-new-branch-icon{position:absolute;top:9px;left:10px;color:#b0c4ce}.quick-pull-choice.will-create-branch .quick-pull-branch-name{display:inline-block}.boxed-group{position:relative;margin-bottom:30px;border-radius:3px}.boxed-group .counter{color:#fff;background-color:#babec0}.boxed-group.flush .boxed-group-inner{padding:0}.boxed-group.condensed .boxed-group-inner{padding:0;font-size:12px}.boxed-group>h3,.boxed-group .heading{display:block;padding:9px 10px 10px;margin:0;font-size:14px;line-height:17px;background-color:#f5f5f5;border:1px solid #d8d8d8;border-bottom:0;border-radius:3px 3px 0 0}.boxed-group>h3.border-light,.boxed-group .heading.border-light{border-color:#e5e5e5}.boxed-group>h3 a,.boxed-group .heading a{color:inherit}.boxed-group>h3 a.boxed-group-breadcrumb,.boxed-group .heading a.boxed-group-breadcrumb{font-weight:normal;color:#666;text-decoration:none}.boxed-group>h3 .avatar,.boxed-group .heading .avatar{margin-top:-4px}.boxed-group .tabnav.heading{padding:0}.boxed-group .tabnav.heading .tabnav-tab.selected{border-top:0}.boxed-group .tabnav.heading li:first-child .selected{border-left-color:#fff;border-top-left-radius:3px}.boxed-group .tabnav-tab{border-top:0;border-radius:0}.boxed-group code.heading{font-size:12px}.boxed-group.dangerzone>h3{color:#fff;text-shadow:0 -1px 0 #900;background-color:#df3e3e;border:1px solid #a00}.boxed-group.dangerzone .boxed-group-inner{border-top:0}.boxed-group.condensed>h3{padding:6px 6px 7px;font-size:12px}.boxed-group.condensed>h3 .octicon{padding:0 6px 0 2px}.one-half .boxed-group,.dashboard-sidebar .boxed-group{margin-bottom:20px}.boxed-group .bleed-flush{width:100%;padding:0 10px;margin-left:-10px}.boxed-group .compact{margin-top:10px;margin-bottom:10px}.boxed-group-inner{padding:10px;font-size:13px;color:#666;background:#fff;border:1px solid #d8d8d8;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.boxed-group-inner .markdown-body{padding:20px 10px 10px;font-size:13px}.boxed-group-inner.markdown-body{padding-top:10px;padding-bottom:10px}.boxed-group-inner.inline-error{padding-top:40px;padding-bottom:40px;text-align:center}.boxed-group-inner.seamless{padding:0}.boxed-group-inner .tabnav{padding-right:10px;padding-left:10px;margin-right:-10px;margin-left:-10px}.boxed-group-inner .tabnav-tab.selected{border-top:1px solid #ddd}.boxed-group-inner .help{padding:1em 10px 1em 35px;margin:1em -10px -10px;clear:both;color:#767676;border-top:1px solid #ddd}.boxed-group-inner .help .octicon{margin-right:5px;margin-left:-25px}.boxed-group-inner .boxed-group-list+.help{margin-top:0}.boxed-group-inner .flash-global{margin-right:-10px;margin-left:-10px;border-top:0}.boxed-action{float:right;margin-left:10px}.boxed-group-action{position:relative;z-index:2;float:right;margin:6px 10px 0 0}.boxed-group-action.flush{margin-top:0;margin-right:0}.field-with-errors{display:inline}.compact-options{margin:-6px 0 13px}.compact-options>li{display:inline-block;margin:0 12px 0 0;font-weight:bold;list-style-type:none}.compact-options>li label{float:left}.compact-options>li .spinner{display:block;float:left;width:16px;height:16px;margin-left:5px}.boxed-group-list{margin:0;list-style:none}.boxed-group-list:first-child>li:first-child{border-top:0}.boxed-group-list>li{display:block;padding:5px 10px;margin-right:-10px;margin-left:-10px;line-height:23px;border-bottom:1px solid #e5e5e5}.boxed-group-list>li:first-child{border-top:1px solid #ddd}.boxed-group-list>li:last-of-type{border-bottom:0}.boxed-group-list>li.selected{background:#e5f9e2}.boxed-group-list>li.approved .btn-sm,.boxed-group-list>li.rejected .btn-sm{display:none}.boxed-group-list>li.rejected a{text-decoration:line-through}.boxed-group-list>li img{margin-top:-2px;margin-right:4px;vertical-align:middle;border-radius:3px}.boxed-group-list>li .btn-sm{float:right;margin:-1px 0 0 10px}.boxed-group-list>li .btn-group{float:right}.boxed-group-list>li .btn-group .btn-sm{float:left}.boxed-group.flush .boxed-group-list li{width:auto;padding-right:0;padding-left:0;margin-left:0}.boxed-group-list.standalone{margin-top:-1px}.boxed-group-list.standalone>li:first-child{border-top:0}.boxed-group-standalone{margin-top:-10px;margin-bottom:-10px}.boxed-group-standalone>li:last-child{border-radius:0 0 2px 2px}.boxed-group-table{width:100%;text-align:left}.boxed-group-table tr:last-child td{border-bottom:0}.boxed-group-table th{padding:9px;background-color:#fafafa;border-bottom:1px solid #eee}.boxed-group-table td{padding:9px;vertical-align:top;border-bottom:1px solid #eee}.ajax-error-message{position:fixed;top:-200px;left:50%;z-index:9999;display:none;width:974px;margin:0 3px;margin-left:-487px;-webkit-transition:top 0.5s ease-in-out;transition:top 0.5s ease-in-out}.ajax-error-message.visible{top:0}.ajax-error-message>.octicon-alert{vertical-align:text-top}.boxed-group-success,.boxed-group-warning,.boxed-group-info{padding:10px 15px;margin:-10px -10px 10px;border-style:solid;border-width:1px 0}.boxed-group-success .btn-sm,.boxed-group-warning .btn-sm,.boxed-group-info .btn-sm{margin:-5px 0}.boxed-group-success:first-child,.boxed-group-warning:first-child,.boxed-group-info:first-child{border-top:0}.boxed-group-success{color:#22662c;background-color:#e2f9e5;border-color:#bad3be}.boxed-group-warning{color:#4c4a42;background-color:#fff9ea;border-color:#dfd8c2}.boxed-group-info{color:inherit;border-color:inherit}.branch-name{display:inline-block;padding:2px 6px;font:12px Consolas, "Liberation Mono", Menlo, Courier, monospace;color:rgba(0,0,0,0.5);background-color:rgba(209,227,237,0.5);border-radius:3px}.branch-name .octicon{margin:1px -2px 0 0;color:#b0c4ce}a.branch-name{color:#4078c0}.breadcrumb{margin-bottom:10px;font-size:18px;color:#767676}.breadcrumb .separator::before,.breadcrumb .separator::after{content:" "}.breadcrumb strong.final-path{color:#333}.breadcrumb .zeroclipboard-button{display:inline-block;margin-left:5px}.breadcrumb .repo-root{font-weight:bold}.breadcrumb .octicon{vertical-align:-2px}.editor-license-template,.editor-gitignore-template{position:relative;top:3px;display:none;float:right;font-size:14px}.editor-license-template.is-visible,.editor-gitignore-template.is-visible{display:block}.editor-license-template .select-menu-git-ignore,.editor-license-template .select-menu-license-picker,.editor-gitignore-template .select-menu-git-ignore,.editor-gitignore-template .select-menu-license-picker{right:0}.starring-container .unstarred,.starring-container.on .starred{display:block}.starring-container.on .unstarred,.starring-container .starred{display:none}.starring-container.loading{opacity:0.5}.user-following-container .follow,.user-following-container.on .unfollow{display:inline-block}.user-following-container.on .follow,.user-following-container .unfollow{display:none}.user-following-container.loading{opacity:0.5}.members .user-following-container{float:right}.close-button{padding:0;background:transparent;border:0;outline:none}.btn-invisible{color:#4078c0;background-color:#fff;background-image:none;border:0}.btn-invisible:hover,.btn-invisible:active,.btn-invisible:focus,.btn-invisible.selected,.btn-invisible.zeroclipboard-is-hover,.btn-invisible.zeroclipboard-is-active{color:#4078c0;background:none;outline:none;box-shadow:none}.btn-octicon{display:inline-block;padding:5px;margin-left:5px;line-height:1;color:#767676;vertical-align:middle;background:transparent;border:0;outline:none}.btn-octicon:hover{color:#4078c0}.btn-octicon.disabled{color:#bbb;cursor:default}.btn-octicon.disabled:hover{color:#bbb}.btn-octicon-danger:hover{color:#bd2c00}.commit-ref{position:relative;display:inline-block;padding:0 5px;font:0.75em/2 Consolas, "Liberation Mono", Menlo, Courier, monospace;color:#336479;white-space:nowrap;background-color:#e8f0f8;border-radius:3px}.commit-ref .user{color:#598a9f}a.commit-ref:hover{text-decoration:none;text-shadow:-1px -1px 0 rgba(0,0,0,0.2);background-image:-webkit-linear-gradient(#74a4d4, #2a5177);background-image:linear-gradient(#74a4d4, #2a5177);border-color:#2a5177}.capped-cards{list-style:none}.capped-cards .capped-card{float:left;width:450px}.capped-card{margin:10px;list-style:none;border:1px solid #ddd;border-radius:2px}.capped-card::before{display:table;content:""}.capped-card::after{display:table;clear:both;content:""}.capped-card:nth-child(odd){margin-left:0}.capped-card:nth-child(even){margin-right:0}.capped-card h3{padding:10px;margin:0;line-height:100%;border-bottom:1px solid #eee}.capped-card>p{display:block;padding:0 10px 10px;margin:0;font-size:15px;line-height:100%;color:#767676;border-bottom:1px solid #eee}.capped-card-content{display:block;background:#f7f7f7}.capped-card-content::before{display:table;content:""}.capped-card-content::after{display:table;clear:both;content:""}.clone-url h5{margin-top:0;margin-bottom:10px}.clone-url .input-group{width:100%}.file-editor-textarea{width:100%;padding:5px 4px;font:12px Consolas, "Liberation Mono", Menlo, Courier, monospace;resize:vertical;border:0;border-radius:0;outline:none}.container-preview .tabnav-tabs{margin:-6px 0 -6px -11px}.container-preview .tabnav-tabs .tabnav-tab{padding:12px 15px;border-radius:0}.container-preview .tabnav-tabs>.selected:first-child{border-top-left-radius:3px}.container-preview .tabnav-tabs .selected{font-weight:bold}.container-preview.show-code .commit-create,.container-preview.show-code .actions{display:block}.container-preview.show-code .commit-preview,.container-preview.show-code .loading-preview-msg,.container-preview.show-code .no-changes-preview-msg,.container-preview.show-code .error-preview-msg{display:none}.container-preview:not(.show-code) .commit-create,.container-preview:not(.show-code) .actions{display:none}.container-preview.loading-preview .loading-preview-msg{display:block}.container-preview.loading-preview .no-changes-preview-msg,.container-preview.loading-preview .error-preview-msg,.container-preview.loading-preview .commit-preview{display:none}.container-preview.show-preview .commit-preview{display:block}.container-preview.show-preview .loading-preview-msg,.container-preview.show-preview .no-changes-preview-msg,.container-preview.show-preview .error-preview-msg{display:none}.container-preview.no-changes-preview .no-changes-preview-msg{display:block}.container-preview.no-changes-preview .loading-preview-msg,.container-preview.no-changes-preview .error-preview-msg,.container-preview.no-changes-preview .commit-preview{display:none}.container-preview.error-preview .error-preview-msg{display:block}.container-preview.error-preview .loading-preview-msg,.container-preview.error-preview .no-changes-preview-msg,.container-preview.error-preview .commit-preview{display:none}.container-preview p.preview-msg{padding:30px;font-size:16px}.ace_editor.ace-github-light{position:relative;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px;line-height:18px}.ace_editor.ace-github-light .ace_scroller.ace_scroll-left{box-shadow:none}.ace_gutter{border-right:1px solid #eee}.ace_gutter-layer{min-width:50px}.ace_nobold .ace_line>span{font-weight:normal !important}.ace_marker-layer .ace_step{background-color:#fcff00}.ace_marker-layer .ace_stack{background-color:#a4e565}.ace_marker-layer .ace_selected-word{background-color:#fafaff}.ace_indent-guide{box-shadow:inset -1px 0 0 rgba(0,0,0,0.1)}.code-list .file-box{border:1px solid #ddd;border-radius:3px}.code-list em{padding:2px;margin:0 -2px;font-style:normal;font-weight:bold;color:#333;background-color:rgba(255,255,140,0.5);border-radius:3px}.code-list .title{min-height:24px;margin:-3px 0 10px 38px;font-weight:bold;line-height:1.2}.code-list .repo-specific .title,.code-list .repo-specific .full-path{margin-left:0}.code-list .match-count,.code-list .updated-at{margin:0;font-weight:normal}.code-list .language{float:right;margin-left:10px;font-size:12px;color:rgba(51,51,51,0.75)}.code-list .avatar{float:left}.code-list .code-list-item+.code-list-item{padding-top:20px;margin-top:20px;margin-bottom:10px;border-top:1px solid #eee}.code-list .blob-num{padding:0}.code-list .blob-num::before{content:normal}.code-list .blob-num a{padding:0 10px;color:inherit}.code-list .blob-num a:hover{color:#4078c0}.code-list .blob-code{white-space:pre-wrap}.code-list .divider .blob-num,.code-list .divider .blob-code{padding-top:0;padding-bottom:0;cursor:default;background-color:#f8fafd}.code-list .divider .blob-num{height:18px;padding:0 10px;line-height:15px;background-color:#f0f5fa}.code-list .full-path{margin:0 0 0 40px}.code-list .full-path .octicon-repo{color:#767676}.code-list .full-path .octicon-lock{color:#e9dba4}.code-list .full-path a{color:#999}.code-list-item-private .file-box{border:1px solid #fadda5}.code-list-item-private .blob-num{background-color:#fff9ea;border-right:1px solid #fadda5}.code-list-item-private .blob-num a{color:#4c4a42}.code-list-item-private .divider .blob-num,.code-list-item-private .divider .blob-code{color:#4c4a42;background-color:#fff9ea}.details-collapse .collapse{position:relative;display:none;height:0;overflow:hidden;-webkit-transition:height 0.35s ease-in-out;transition:height 0.35s ease-in-out}.details-collapse.open .collapse{display:block;height:auto;overflow:visible}.comment .email-format{line-height:1.5}.previewable-edit .previewable-comment-form{display:none}.previewable-edit .previewable-comment-form::before{display:table;content:""}.previewable-edit .previewable-comment-form::after{display:table;clear:both;content:""}.previewable-edit .previewable-comment-form .tabnav-tabs{display:inline-block}.previewable-edit .previewable-comment-form .form-actions{float:right;margin-right:10px;margin-bottom:10px}.previewable-edit.is-comment-editing .timeline-comment-header{display:none}.is-comment-editing .timeline-comment-actions,.is-comment-editing .edit-comment-hide{display:none}.is-comment-editing .previewable-comment-form{display:block}.is-comment-loading .previewable-comment-form{opacity:0.5}.is-comment-stale .comment-form-stale{display:block}.comment-body{width:100%;padding:15px;overflow:visible;font-size:14px}.comment-body .highlight{overflow:visible !important;background-color:transparent}.comment-form-textarea{width:100%;max-width:100%;height:100px;min-height:100px;margin:0;font-size:14px;line-height:1.6}.comment-form-textarea.dragover{border:solid 1px #4078c0}.discussion-topic-header{position:relative;padding:10px;word-wrap:break-word}.comment-form-error,.comment-form-stale{display:none;padding:15px 10px;margin:10px;color:#900;border:1px solid #e2a0a0;border-radius:3px}.comment-form-error.comment-form-bottom,.comment-form-stale.comment-form-bottom{margin-bottom:10px}.email-format{line-height:1.5em !important}.email-format div{white-space:pre-wrap}.email-format .email-hidden-reply{display:none;white-space:pre-wrap}.email-format .email-hidden-reply.expanded{display:block}.email-format .email-quoted-reply,.email-format .email-signature-reply{padding:0 15px;margin:15px 0;color:#767676;border-left:4px solid #ddd}.email-format .email-hidden-toggle a{display:inline-block;height:12px;padding:0 9px;font-size:12px;font-weight:bold;line-height:6px;color:#555;text-decoration:none;vertical-align:middle;background:#ddd;border-radius:1px}.email-format .email-hidden-toggle a:hover{background-color:#ccc}.email-format .email-hidden-toggle a:active{color:#fff;background-color:#4078c0}.comment-email-format div{white-space:normal}.comment-email-format .email-hidden-reply{display:none;white-space:normal}.comment-email-format .email-hidden-reply.expanded{display:block}.comment-email-format blockquote,.comment-email-format p{margin:0}.blankslate.conversation-limited{padding:20px 0 10px;margin:15px}.locked-conversation .write-tab,.locked-conversation .preview-tab{color:#ccc}.commit-form{position:relative;padding:15px;border:1px solid #ddd;border-radius:3px}.commit-form::after,.commit-form::before{position:absolute;top:11px;right:100%;left:-16px;display:block;width:0;height:0;pointer-events:none;content:" ";border-color:transparent;border-style:solid solid outset}.commit-form::after{margin-top:1px;margin-left:2px;border-width:7px;border-right-color:#fff}.commit-form::before{border-width:8px;border-right-color:#ddd}.commit-form .input-block{margin-top:10px;margin-bottom:10px}.commit-form-avatar{float:left;margin-left:-64px;border-radius:3px}.commit-form-actions::before{display:table;content:""}.commit-form-actions::after{display:table;clear:both;content:""}.commit-form-actions .btn-group{margin-right:5px}.commit-form-actions .check-for-fork{line-height:34px}.merge-commit-message{resize:vertical}.commit-sha{padding:0.2em 0.4em;font-size:90%;font-weight:normal;background-color:#f5f5f5;border:1px solid #eee;border-radius:0.2em}.commit-partial-notice{margin-top:20px;margin-bottom:20px}.commit-paginate-container{float:right;margin:-5px 0 0;text-align:inherit}.commit .commit-title,.commit .commit-title a{color:#4e575b}.commit .commit-title.blank,.commit .commit-title.blank a{color:#9cabb1}.commit .commit-title .issue-link{font-weight:bold;color:#4078c0}.commit .sha-block,.commit .sha{font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px}.commit.open .commit-desc{display:block}.commit-link{font-weight:normal;color:#4078c0}.commit-email-flash{display:inline}.commit-desc{display:none}.commit-desc pre{max-width:700px;margin-top:10px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:11px;line-height:1.45;color:#596063;white-space:pre-wrap}.commit-desc+.commit-branches{padding-top:8px;margin-top:2px;border-top:solid 1px #d1e2eb}.commit-author-section{color:#333}.commit-author-section span.user-mention{font-weight:normal}.commit-tease{position:relative;padding:10px;margin-bottom:-1px;line-height:20px;color:#68777d;background-color:#f2f9fc;border:1px solid #c9e6f2;border-radius:3px;border-bottom-right-radius:0;border-bottom-left-radius:0}.commit-tease .muted-link{color:inherit}.commit-tease .loader{float:left;margin:2px 5px 0 2px}.commit-tease .message{color:inherit}.commit-tease .avatar{margin-top:-1px}.commit-tease.open .commit-desc{display:block}.branch-infobar+.commit-tease{border-top-left-radius:0;border-top-right-radius:0}.commit-tease-comments{margin-right:15px}.commit-tease-sha{display:inline-block;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;color:#445055}.commit-tease-contributors{padding:5px 10px;margin:10px -10px -10px;background-color:#fff;border-top:1px solid #c9e6f2;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.commit-tease-contributors::before{display:table;content:""}.commit-tease-contributors::after{display:table;clear:both;content:""}.commit-tease-contributors .contributors-toggle{float:left;margin-right:10px}.commit-tease-contributors .avatar-link{float:left;margin-right:3px}.commit-tease-contributors .loader-loading{margin:2px 5px 2px 0}.commit-tease-contributors.error .loader-loading{display:none}.commit-tease-contributors.error .loader-error{display:block}.commits-listing{position:relative;padding-bottom:20px;margin-bottom:15px}.commits-listing::before{position:absolute;top:0;bottom:0;left:14px;z-index:-1;display:block;width:2px;content:"";background-color:#f3f3f3}.commits-listing .discussion-item-icon{margin-right:5px;margin-left:-1px}.commits-listing .timeline-commits{padding-left:8px;margin-bottom:20px}.commits-listing .timeline-commits:last-child{margin-bottom:0}.commits-listing-padded{padding-left:39px}.commit-group{margin-top:10px;list-style-type:none}.commit-group-title{margin-top:15px;margin-left:-31px;color:#767676}.commit-group-title .octicon-git-commit{margin-right:17px;color:#ccc;background:#fff}.commits-list-item.navigation-focus{background:#f7fbfc}.commits-list-item .commit-title{margin:0;font-size:15px;font-weight:bold;color:#333}.commits-list-item .commit-meta{margin-top:1px;font-weight:normal;color:#767676}.commits-list-item .status .octicon{height:14px;line-height:14px}.commits-list-item .commit-author{color:#767676}.commits-list-item .octicon-arrow-right{margin:0 3px}.commits-list-item .btn-outline{margin-top:2px}.commits-list-item .commit-desc pre{margin-top:5px;margin-bottom:10px;color:#767676}.commits-list-item .commit-desc pre a{word-break:break-word}.commits-comments-link{margin-top:9px;color:#767676;vertical-align:middle}.commits-comments-link:hover{color:#4183c4;text-decoration:none}.commit-avatar-cell{width:47px}.commit-avatar-cell.table-list-cell{padding-right:0}.commit-indicator{margin-left:5px}.commit-links-cell{width:310px;text-align:right}.commit-links-group{margin-right:5px}.timeline-commits{width:100%;margin-top:5px;border-collapse:separate}.timeline-commits+.timeline-commits{margin-top:15px}.timeline-commits td{padding-top:4px;padding-right:8px;padding-bottom:4px;font-size:12px;line-height:16px;vertical-align:top;background-color:transparent}.discussion-item .timeline-commits .commit-author{display:none}.timeline-commits .commit-gravatar{width:16px;padding-left:10px}.timeline-commits .commit-author{width:200px;padding-right:20px;white-space:nowrap}.timeline-commits .author{font-weight:bold;color:#555}.timeline-commits .commit-message{max-width:550px;min-height:0}.timeline-commits .commit-message>code a{color:#555}.timeline-commits .commit-message>code a:hover{color:#4078c0}.timeline-commits .commit-desc pre{overflow:visible;color:#767676}.timeline-commits .hidden-text-expander{margin-top:3px;margin-left:0;vertical-align:top}.timeline-commits .hidden-text-expander .ellipsis-expander{height:13px;background-color:#eee}.timeline-commits .hidden-text-expander .ellipsis-expander:hover{color:#fff;background-color:#4078c0}.timeline-commits .commit-sig-status{width:60px;padding-right:4px;text-align:right}.timeline-commits .commit-ci-status{width:16px;padding-right:4px}.timeline-commits .commit-ci-status .octicon{margin-right:1px;margin-left:1px;vertical-align:bottom}.timeline-commits .commit-ci-status .octicon-primitive-dot{width:9px;margin-right:3px}.timeline-commits .commit-meta{width:50px;text-align:right}.commit-icon{display:table-cell;width:16px;color:#ccc}.commit-icon .octicon{background-color:#fff}.commit-id{color:#bbb}.commit-id:hover{color:#4078c0}.full-commit{padding:8px 8px 0;margin:10px 0;font-size:14px;background:#e6f1f6;border:1px solid #c5d5dd;border-radius:3px}.full-commit:first-child{margin-top:0}.full-commit .btn-outline,.full-commit .btn-outline:disabled{background-color:transparent;border:1px solid #cedee5}.full-commit .btn-outline:not(:disabled):hover{color:#4078c0;border:1px solid #4078c0}.full-commit p.commit-title{margin:0 0 8px;font-size:18px;font-weight:bold;color:#213f4d}.full-commit .branches-list{display:inline;margin-right:10px;margin-left:2px;vertical-align:middle;list-style:none}.full-commit .branches-list li{display:inline-block;padding-left:3px;font-weight:bold;color:#596063}.full-commit .branches-list li::before{padding-right:6px;font-weight:normal;content:"+"}.full-commit .branches-list li:first-child{padding-left:0}.full-commit .branches-list li:first-child::before{padding-right:0;content:""}.full-commit .branches-list li.loading{font-weight:normal;color:#818c90}.full-commit .branches-list li.pull-request{font-weight:normal;color:#818c90}.full-commit .branches-list li.pull-request::before{margin-left:-8px;content:""}.full-commit .branches-list li.pull-request-error{margin-bottom:-1px}.full-commit .branches-list li a{color:inherit}.full-commit .commit-meta{padding:8px;margin-right:-8px;margin-left:-8px;background:#fff;border-top:1px solid #d8e6ec;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.full-commit .sha-block{margin-left:15px;font-size:12px;line-height:24px;color:#767676}.full-commit .sha-block>.sha{color:#444}.full-commit .sha-block>a{color:#444;text-decoration:none;border-bottom:1px dotted #ccc}.full-commit .sha-block>a:hover{border-bottom:1px solid #444}.full-commit .commit-desc{display:block;margin:-5px 0 10px}.full-commit .commit-desc pre{max-width:100%;overflow:visible;font-size:13px;word-wrap:break-word}.branches-tag-list{display:inline;margin-right:10px;margin-left:2px;vertical-align:middle;list-style:none}.branches-tag-list .more-commit-details,.branches-tag-list.open .hidden-text-expander{display:none}.branches-tag-list.open .more-commit-details{display:inline-block}.branches-tag-list li{display:inline-block;padding-left:3px}.branches-tag-list li:first-child{padding-left:0;font-weight:bold;color:#596063}.branches-tag-list li.loading{font-weight:normal;color:#818c90}.branches-tag-list li.abbrev-tags{cursor:pointer}.branches-tag-list li a,.branches-tag-list li .ellipsis-expander{color:inherit}.branches-tag-list li .ellipsis-expander{background-color:#dae5eb}.branches-tag-list li .ellipsis-expander:hover{background-color:#d1dbe0}.commit-branches{min-height:18px;margin-top:-6px;margin-bottom:8px;font-size:12px;color:#818c90;vertical-align:middle}.commit-branches .octicon{vertical-align:middle}.commit-loader .loader-error{display:none;margin:0;font-size:12px;font-weight:bold;color:#bd2c00}.commit-loader.error .loader-loading{display:none}.commit-loader.error .loader-error{display:block}.historical-banner{padding:15px 20px 15px 130px;margin-bottom:20px;overflow:hidden;color:#333;background:#fff;border:1px solid #e5e5e5;border-radius:5px}.historical-banner h2{margin:0 0 5px}.historical-banner p{margin:0}.historical-banner .illustration{position:absolute;top:12px;left:20px;color:rgba(0,0,0,0.1)}.roses-divider{margin-bottom:20px;text-align:center}.commit-comments-heading{max-width:780px;margin-bottom:15px}.commit-comment-count{display:inline-block;margin-right:15px;margin-bottom:0}.commit-build-statuses{position:relative;display:inline-block;text-align:left}.commit-build-statuses .octicon-primitive-dot{width:10px}.commit-build-statuses.active .dropdown-menu-content{display:block}.commit-build-statuses.active .tooltipped::before,.commit-build-statuses.active .tooltipped::after{display:none}.commit-build-statuses .dropdown-menu{min-width:400px;max-width:500px;padding-top:0;padding-bottom:0}.commit-build-statuses .dropdown-menu .build-statuses-list{max-height:170px;border-bottom:0}.commit-build-statuses .dropdown-menu-w,.commit-build-statuses .dropdown-menu-e{top:-11px}.commit-build-statuses .build-status-item:last-child{border-radius:0 0 2px 2px}.dropdown-signed-commit{display:inline-block}.dropdown-signed-commit .dropdown-menu{width:260px;padding-top:0;padding-bottom:0;margin-top:7px;font-size:13px;line-height:1.4;color:#333;text-align:left}.dropdown-signed-commit .dropdown-menu::after{border-bottom-color:#f9f9f9}.dropdown-signed-commit .dropdown-menu-w{top:-28px;margin-top:0}.dropdown-signed-commit .dropdown-menu-w::after{border-bottom-color:transparent;border-left-color:#f9f9f9}.signed-commit-header{padding-top:12px;padding-bottom:12px;line-height:1.3;white-space:normal;border-collapse:separate;background-color:#f9f9f9;border-bottom:solid 1px #e5e5e5;border-top-left-radius:3px;border-top-right-radius:3px}.signed-commit-header .octicon-verified{color:#6cc644}.signed-commit-header .octicon-unverified{color:#bbb}.signed-commit-signer{padding-right:12px;padding-left:12px;margin-top:12px;border-collapse:separate}.signed-commit-footer{padding:12px;font-size:12px;line-height:1.5}.signed-commit-cert-info{margin-bottom:6px}.signed-commit-cert-info td{vertical-align:top}.signed-commit-cert-info td:first-child{width:44px;padding-right:12px}.signed-commit-badge{display:inline-block;padding:1px 4px;font-size:10px;color:#767676;vertical-align:middle;background:none;border:solid 1px #e5e5e5;border-radius:2px}.signed-commit-badge:hover{text-decoration:none;border-color:#ccc}.signed-commit-badge:focus{outline:none}.signed-commit-badge.verified{color:#55a532}.signed-commit-badge.verified:hover{border-color:#6cc644}.signed-commit-badge-small{margin-top:-2px;margin-right:3px}.signed-commit-badge-medium{padding:3px 8px;font-size:12px;border-radius:3px}.signed-commit-badge-large{padding:6px 12px;margin-top:2px;margin-right:9px;font-size:13px;line-height:20px;border-radius:3px}.signed-commit-verified-label{color:#55a532;white-space:nowrap}.signed-commit-signer-name{font-size:14px;text-align:left}.signed-commit-signer-name .signer{display:block;font-weight:bold;color:#333}.table-of-contents{margin:15px 0}.table-of-contents li{padding:7px 0;list-style-type:none}.table-of-contents li+li{border-top:1px solid #eee}.table-of-contents li>.octicon{margin-right:3px}.table-of-contents .octicon-diff-removed{color:#bd2c00}.table-of-contents .octicon-diff-renamed{color:#677a85}.table-of-contents .octicon-diff-modified{color:#d0b44c}.table-of-contents .octicon-diff-added{color:#6cc644}.toc-select .select-menu-modal{width:420px}.toc-select .select-menu-item-heading{color:#333}.toc-select .select-menu-item-icon.octicon-diff-removed{color:#bd2c00}.toc-select .select-menu-item-icon.octicon-diff-renamed{color:#677a85}.toc-select .select-menu-item-icon.octicon-diff-modified{color:#d0b44c}.toc-select .select-menu-item-icon.octicon-diff-added{color:#6cc644}.toc-select .navigation-focus .select-menu-item-heading,.toc-select .navigation-focus .text-diff-added,.toc-select .navigation-focus .text-diff-deleted,.toc-select .navigation-focus .octicon-diff-removed,.toc-select .navigation-focus .octicon-diff-renamed,.toc-select .navigation-focus .octicon-diff-modified,.toc-select .navigation-focus .octicon-diff-added,.toc-select .navigation-focus .diffstat{color:#fff}.toc-select .description{max-width:320px}.toc-diff-stats{padding-left:20px;line-height:26px}.toc-diff-stats .octicon{float:left;margin-top:3px;margin-left:-20px;color:#ccc}.toc-diff-stats .btn-link{font-weight:bold}.toc-diff-stats+.content{padding-top:5px}.conversation-list-heading{height:0;margin:35px 0 10px;font-size:16px;font-weight:normal;color:#999;text-align:center;border-bottom:1px solid #ddd}.conversation-list-heading .inner{position:relative;top:-10px;display:inline-block;padding:0 5px;background:#fff}.simple-conversation-list{margin:15px 0;font-size:13px;color:#999}.simple-conversation-list>li{padding:11px 0 8px;margin:0;list-style-type:none;border-top:1px solid #eee}.simple-conversation-list>li:first-child{border-top:0}.simple-conversation-list>li .title{font-weight:bold}.simple-conversation-list>li .num{color:#999}.simple-conversation-list>li .state{padding-top:2px;padding-bottom:2px;margin-top:-3px;margin-right:3px}.simple-conversation-list>li .meta{float:right;margin-left:10px}.simple-conversation-list.varied-states>li{padding-left:90px}.simple-conversation-list.varied-states>li::before{display:table;content:""}.simple-conversation-list.varied-states>li::after{display:table;clear:both;content:""}.simple-conversation-list.varied-states>li .state{float:left;width:80px;margin-left:-90px}.copyable-terminal{position:relative;padding:10px 55px 10px 10px;background-color:#f7f7f7;border-radius:3px}.copyable-terminal-content{overflow:auto}.copyable-terminal-button{position:absolute;top:5px;right:5px}.copyable-terminal-button .zeroclipboard-button{float:right}.copyable-terminal-button .zeroclipboard-button .octicon{padding-left:1px;margin:0 auto}.date-selector{z-index:9;display:none;width:225px;text-align:left;text-decoration:none}.date-selector .month-nav,.date-selector .year-nav{position:relative;display:block;padding:0;margin-top:5px;margin-bottom:5px;line-height:20px;text-align:center}.date-selector .month-nav{float:left;width:55%}.date-selector .year-nav{float:right;width:35%}.date-selector .date-button{position:absolute;top:0;width:18px;height:18px;padding:4px;font-size:12px;line-height:12px;color:#4078c0;cursor:pointer}.date-selector .prev{left:0}.date-selector .next{right:0}.date-selector table{width:100%;clear:both}.date-selector tr{font-size:0}.date-selector th,.date-selector td{display:inline-block;width:32px;height:32px;padding:0;margin-top:-1px;margin-left:-1px;font-size:12px;font-weight:normal;line-height:28px;text-align:center}.date-selector td{color:#4078c0;cursor:default;background:#fff;border:1px solid #ccc}.date-selector td.today{background:#eee}.date-selector td.selected,.date-selector td.selectable-day:hover{position:relative;z-index:10;color:#fff;cursor:pointer;background:#4078c0;border-color:#33609a}.date-selector td.unselected-month{color:#ccc}.blob-wrapper{overflow-x:auto;overflow-y:hidden;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.diff-table{width:100%;border-collapse:separate}.diff-table .line-comments{padding:10px;vertical-align:top;border-top:1px solid #eee}.diff-table .line-comments:first-child+.empty-cell{border-left-width:1px}.diff-table tr:not(:last-child) .line-comments{border-top:1px solid #eee;border-bottom:1px solid #eee}.blob-num{width:1%;min-width:50px;padding-right:10px;padding-left:10px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px;line-height:18px;color:rgba(0,0,0,0.3);text-align:right;white-space:nowrap;vertical-align:top;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:solid #eee;border-width:0 1px 0 0}.blob-num:hover{color:rgba(0,0,0,0.6)}.blob-num::before{content:attr(data-line-number)}.blob-num.non-expandable{cursor:default}.blob-num.non-expandable:hover{color:rgba(0,0,0,0.3)}.blob-code{position:relative;padding-right:10px;padding-left:10px;vertical-align:top}.blob-code-inner{overflow:visible;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px;color:#333;word-wrap:normal;white-space:pre}.blob-code-inner .x-first{border-top-left-radius:0.2em;border-bottom-left-radius:0.2em}.blob-code-inner .x-last{border-top-right-radius:0.2em;border-bottom-right-radius:0.2em}.blob-code-inner::before{content:""}.soft-wrap .diff-table{table-layout:fixed}.soft-wrap .blob-code{padding-left:18px;text-indent:-7px}.soft-wrap .blob-code-inner{word-wrap:break-word;white-space:pre-wrap}.soft-wrap .no-nl-marker{display:none}.soft-wrap .add-line-comment{margin-left:-28px}.blob-num-hunk,.blob-code-hunk,.blob-num-expandable,.blob-code-expandable{color:rgba(0,0,0,0.3);vertical-align:middle;border-color:#d2dff0}.blob-num-hunk,.blob-num-expandable{background-color:#edf2f9}.blob-code-hunk,.blob-code-expandable{padding-top:4px;padding-bottom:4px;background-color:#f4f7fb;border-width:1px 0}.blob-expanded .blob-num,.blob-expanded .blob-code{background-color:#fafafa}.blob-expanded+tr:not(.blob-expanded) .blob-num,.blob-expanded+tr:not(.blob-expanded) .blob-code{border-top:1px solid #eee}.blob-expanded .blob-num-hunk{border-top:1px solid #eee}tr:not(.blob-expanded)+.blob-expanded .blob-num,tr:not(.blob-expanded)+.blob-expanded .blob-code{border-top:1px solid #eee}.blob-num-expandable{padding:0;font-size:12px;text-align:center}.blob-num-expandable .octicon{vertical-align:top}.blob-num-expandable .diff-expander{display:block;width:auto;height:auto;padding:4px 11px 4px 10px;margin-right:-1px;color:#767676;cursor:pointer}.blob-num-expandable .diff-expander:hover{color:#fff;text-shadow:none;background-color:#4078c0;border-color:#4078c0}.blob-code-addition{background-color:#eaffea}.blob-code-addition .x{color:#333;background-color:#a6f3a6}.blob-num-addition{background-color:#dbffdb;border-color:#c1e9c1}.blob-code-deletion{background-color:#ffecec}.blob-code-deletion .x{color:#333;background-color:#f8cbcb}.blob-num-deletion{background-color:#ffdddd;border-color:#f1c0c0}.selected-line.blob-code{background-color:#f8eec7}.selected-line.blob-code .x{background-color:transparent}.selected-line.blob-num{background-color:#f6e8b5;border-color:#f0db88}.add-line-comment{position:relative;z-index:5;float:left;width:22px;height:22px;margin:-2px -10px -2px -20px;line-height:21px;color:#fff;text-align:center;text-indent:0;cursor:pointer;background-color:#4078c0;background-image:-webkit-linear-gradient(#5386c6, #4078c0);background-image:linear-gradient(#5386c6, #4078c0);border-radius:3px;box-shadow:0 1px 4px rgba(0,0,0,0.15);opacity:0;-webkit-transition:-webkit-transform 0.1s ease-in-out;transition:transform 0.1s ease-in-out;-webkit-transform:scale(0.8, 0.8);transform:scale(0.8, 0.8)}.add-line-comment:hover{-webkit-transform:scale(1, 1);transform:scale(1, 1)}.is-hovered .add-line-comment{opacity:1}.add-line-comment .octicon{pointer-events:none}.add-line-comment.octicon-check{background:#333;opacity:1}.inline-comment-form{border:1px solid #ddd;border-radius:3px}.inline-review-comment{margin-top:0 !important;margin-bottom:10px !important}.inline-review-comment .gc:first-child+tr .blob-num,.inline-review-comment .gc:first-child+tr .blob-code{padding-top:5px}.inline-review-comment tr:last-child{border-bottom-right-radius:2px;border-bottom-left-radius:2px}.inline-review-comment tr:last-child .blob-num,.inline-review-comment tr:last-child .blob-code{padding-bottom:8px}.inline-review-comment tr:last-child .blob-num:first-child,.inline-review-comment tr:last-child .blob-code:first-child{border-bottom-left-radius:2px}.inline-review-comment tr:last-child .blob-num:last-child,.inline-review-comment tr:last-child .blob-code:last-child{border-bottom-right-radius:2px}.timeline-inline-comments{width:100%;table-layout:fixed}.timeline-inline-comments .inline-comments,.show-inline-notes .inline-comments{display:table-row}.inline-comments{display:none}.inline-comments.is-collapsed{display:none}.inline-comments .line-comments.is-collapsed{visibility:hidden}.inline-comments .line-comments+.blob-num{border-left-width:1px}.inline-comments .timeline-comment{margin-bottom:10px}.inline-comments .inline-comment-form,.inline-comments .inline-comment-form-container{max-width:780px}.comment-holder{max-width:780px}.line-comments+.line-comments,.empty-cell+.line-comments{border-left:1px solid #eee}.inline-comment-form-container .inline-comment-form,.inline-comment-form-container.open .inline-comment-form-actions{display:none}.inline-comment-form-container .inline-comment-form-actions,.inline-comment-form-container.open .inline-comment-form{display:block}body.split-diff .container{width:100%;padding-right:30px;padding-left:30px}body.split-diff .repository-content{width:100%}body.split-diff .new-pr-form{max-width:980px}body.split-diff .new-pr-form .discussion-sidebar{width:200px}.file-diff-split{table-layout:fixed}.file-diff-split .blob-code+.blob-num{border-left-width:1px}.file-diff-split .blob-code-inner{word-wrap:break-word;white-space:pre-wrap}.file-diff-split .empty-cell{cursor:default;background-color:#fafafa;border-right-color:#eee}.diffstat{font-size:12px;font-weight:bold;color:#666;white-space:nowrap;cursor:default}.block-diff-deleted,.block-diff-added,.block-diff-neutral{display:inline-block;width:8px;height:8px;margin-left:1px}.block-diff-deleted,.text-diff-deleted .block-diff-neutral{background-color:#bd2c00}.block-diff-added,.text-diff-added .block-diff-neutral{background-color:#55a532}.block-diff-neutral{background-color:#ddd}.dropdown{position:relative}.dropdown.active .dropdown-menu-content{display:block;pointer-events:all}.dropdown-caret{display:inline-block;width:0;height:0;vertical-align:-2px;content:"";border:4px solid;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}.dropdown-menu{position:absolute;top:100%;left:0;z-index:100;width:160px;padding-top:5px;padding-bottom:5px;margin-top:2px;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.15);border-radius:4px;box-shadow:0 3px 12px rgba(0,0,0,0.15)}.dropdown-menu::before,.dropdown-menu::after{position:absolute;display:inline-block;content:""}.dropdown-menu::before{border:8px solid transparent;border-bottom-color:rgba(0,0,0,0.15)}.dropdown-menu::after{border:7px solid transparent;border-bottom-color:#fff}.dropdown-item{display:block;padding:4px 10px 4px 15px;overflow:hidden;color:#333;text-overflow:ellipsis;white-space:nowrap}.dropdown-item:hover,.dropdown-item.zeroclipboard-is-hover{color:#fff;text-decoration:none;background-color:#4078c0}.dropdown-item:hover>.octicon,.dropdown-item.zeroclipboard-is-hover>.octicon{color:inherit;opacity:1}.dropdown-signout{width:100%;text-align:left;background:none;border:0}.dropdown-divider{height:1px;margin:8px 1px;background-color:#e5e5e5}.dropdown-header{padding:4px 15px;font-size:12px;color:#767676}.dropdown-menu-content{display:none}.dropdown-menu-content.anim-scale-in{position:relative;z-index:100;pointer-events:none}.dropdown-menu-w{right:100%;left:auto;width:auto;margin-top:0;margin-right:10px}.dropdown-menu-w::before{top:10px;right:-16px;left:auto;border-color:transparent;border-left-color:rgba(0,0,0,0.15)}.dropdown-menu-w::after{top:11px;right:-14px;left:auto;border-color:transparent;border-left-color:#fff}.dropdown-menu-e{left:100%;width:auto;margin-top:0;margin-left:10px}.dropdown-menu-e::before{top:10px;left:-16px;border-color:transparent;border-right-color:rgba(0,0,0,0.15)}.dropdown-menu-e::after{top:11px;left:-14px;border-color:transparent;border-right-color:#fff}.dropdown-menu-ne{top:auto;left:0}.dropdown-menu-ne::before,.dropdown-menu-ne::after{top:auto;right:auto}.dropdown-menu-ne::before{bottom:-8px;left:9px;border-top:8px solid rgba(0,0,0,0.15);border-right:8px solid transparent;border-bottom:0;border-left:8px solid transparent}.dropdown-menu-ne::after{bottom:-7px;left:10px;border-top:7px solid #fff;border-right:7px solid transparent;border-bottom:0;border-left:7px solid transparent}.dropdown-menu-s{right:50%;left:auto;-webkit-transform:translateX(50%);transform:translateX(50%)}.dropdown-menu-s::before{top:-16px;right:50%;-webkit-transform:translateX(50%);transform:translateX(50%)}.dropdown-menu-s::after{top:-14px;right:50%;-webkit-transform:translateX(50%);transform:translateX(50%)}.dropdown-menu-sw{right:0;left:auto}.dropdown-menu-sw::before{top:-16px;right:9px;left:auto}.dropdown-menu-sw::after{top:-14px;right:10px;left:auto}.dropdown-menu-se::before{top:-16px;left:9px}.dropdown-menu-se::after{top:-14px;left:10px}g-emoji{font-family:"Apple Color Emoji", "Segoe UI", "Segoe UI Emoji", "Segoe UI Symbol";font-size:18px;font-weight:normal;line-height:20px;vertical-align:middle}html.emoji-size-boost g-emoji{margin-right:3px}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){html.emoji-size-boost g-emoji{margin-right:0;font-size:21px}}.emoji-icon{display:inline-block;width:20px;height:20px;vertical-align:middle;background-repeat:no-repeat;background-size:20px 20px}.emoji-result{display:inline-block;height:20px;font-size:18px;font-weight:normal;vertical-align:middle}.exploregrid{margin:-15px -15px 20px}.exploregrid::before{display:table;content:""}.exploregrid::after{display:table;clear:both;content:""}.exploregrid-item{float:left;width:305px;height:185px;padding:20px;margin:15px;font-size:14px;color:#555;border:1px solid rgba(0,0,0,0.075);border-bottom-color:rgba(0,0,0,0.125);border-radius:4px;box-shadow:0 1px 2px rgba(0,0,0,0.05),0 5px 10px rgba(0,0,0,0.05);-webkit-transition:border-color 0.1s ease-in-out, box-shadow 0.1s ease-in-out;transition:border-color 0.1s ease-in-out, box-shadow 0.1s ease-in-out}.exploregrid-item:hover{text-decoration:none;border-color:rgba(0,0,0,0.1) rgba(0,0,0,0.1) rgba(0,0,0,0.15);box-shadow:0 1px 3px rgba(0,0,0,0.05),0 8px 15px rgba(0,0,0,0.1)}.exploregrid-item:hover .exploregrid-item-title{color:#4078c0}.exploregrid-item:hover .exploregrid-item-header{opacity:1}.exploregrid-item-mini{width:100%;height:auto;min-height:100px;margin:0 0 15px}.exploregrid-item-mini .exploregrid-item-header{height:10px}.exploregrid-item-mini .exploregrid-item-title{font-size:16px}.exploregrid-item-header{height:20px;margin:-21px -21px 20px;background-color:#f5f5f5;border:1px solid rgba(0,0,0,0.1);border-top-left-radius:4px;border-top-right-radius:4px;opacity:0.8;-webkit-transition:opacity 0.1s ease-in-out;transition:opacity 0.1s ease-in-out}.exploregrid-item-title{margin-top:0;margin-bottom:5px;font-size:20px;font-weight:normal;line-height:1.2;color:#333}.exploregrid-item-meta{display:block;margin-top:15px;font-size:13px;color:#767676}.exploregrid-item-meta-details{margin-right:10px}.exploregrid-item-meta-details .octicon{width:16px;text-align:center}.facebox{position:absolute;top:0;left:0;z-index:100;padding-bottom:40px}.facebox ul{margin-bottom:15px;margin-left:25px}.facebox .facebox-staff-links{padding:10px 15px;margin:-15px -15px 15px;background-color:#f5f5f5;border-bottom:1px solid #e5e5e5}.facebox .facebox-staff-links li{display:inline-block;margin-right:10px;color:#767676;list-style:none}.facebox .facebox-staff-links a{font-weight:bold}.facebox pre{padding:10px;background-color:#eee;border:1px solid #ddd;border-radius:3px}.facebox .shortcuts{width:860px}.facebox .facebox-user-list{max-height:400px;margin-bottom:0;margin-left:0;overflow:auto}.facebox .lineprofiler{width:900px}.facebox .lineprofiler pre{overflow-x:scroll;word-wrap:normal;white-space:pre}.facebox .allocation-trace-facebox{width:900px;padding-right:0;padding-left:0;overflow-x:scroll}.facebox-popup{position:relative;background-color:#fff;background-clip:padding-box;border:1px solid rgba(0,0,0,0.25);border-radius:5px;box-shadow:0 0 18px rgba(0,0,0,0.4)}.facebox-content{width:455px;padding:15px}.facebox-content::before{display:table;content:""}.facebox-content::after{display:table;clear:both;content:""}.facebox-close{position:absolute;top:8px;right:5px;padding:10px;cursor:pointer;background-color:transparent;border:0;opacity:0.25;-webkit-appearance:none;-moz-appearance:none;appearance:none}.facebox-close:hover{opacity:1}.facebox-overlay{position:fixed;top:0;left:0;width:100%;height:100%}.facebox-overlay-hide{z-index:-100}.facebox-overlay-active{z-index:99;background-color:#000}.facebox-loading{min-height:64px;background-image:url("/images/spinners/octocat-spinner-64.gif");background-repeat:no-repeat;background-position:center center}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){.facebox-loading{background-image:url("/images/spinners/octocat-spinner-128.gif");background-size:64px 64px}}.facebox-header{padding:15px;margin:-15px -15px 15px;font-size:18px;font-weight:normal;border-bottom:1px solid #e5e5e5}.facebox-header:focus{outline:none}.facebox-footer{padding:10px 15px;margin:0 -15px -15px;text-align:right;background:#fafafa;border-top:1px solid #e5e5e5;border-bottom-right-radius:5px;border-bottom-left-radius:5px}.facebox-footer .help{margin:0;color:#767676;text-align:center}.facebox-alert,.facebox-danger{padding:10px 15px;margin:-16px -15px 15px;border-style:solid;border-width:1px 0}.facebox-alert{color:#796620;background-color:#f8eec7;border-color:#f2e09a}.facebox-danger{padding-left:40px;color:#9c342e;background-color:#f7d9d7;border-color:#f2c4c2}.facebox-danger .octicon{float:left;margin-left:-25px}.facebox-separator{margin:20px -15px}.facebox-staff-search .hfields{margin-top:0;margin-bottom:0}.facebox-staff-search .hfields .form-control{width:340px;margin-right:0}.facebox-staff-search .hfields .btn{margin-top:29px;margin-right:0}.facebox-staff-search .status-check-list{float:none;margin:15px 0 0}.keyboard-shortcuts{float:right}.keyboard-shortcuts .mini-icon{position:relative;top:2px;margin-left:5px}.keyboard-mappings{font-size:12px;color:#555}.keyboard-mappings th{padding-top:25px;font-size:14px;line-height:1.5;color:#333;text-align:left}.keyboard-mappings tbody:first-child tr:first-child th{padding-top:0}.keyboard-mappings td{padding-top:3px;padding-bottom:3px;line-height:20px;vertical-align:top}.keyboard-mappings .keys{padding-right:10px;color:#767676;text-align:right;white-space:nowrap}.keyboard-mappings .platform-mac{display:none}.macintosh .keyboard-mappings .platform-mac{display:inline}.macintosh .keyboard-mappings .platform-other{display:none}.facebox-user-list-item{padding:3px 0;font-weight:bold;vertical-align:middle;list-style:none}.facebox-user-list-item a{color:#000}.facebox-user-list-item img{margin-right:5px;vertical-align:middle;border-radius:3px}.linejump .linejump-input{width:340px;background-color:#fafafa}.linejump .linejump-input,.linejump .btn{padding:10px 15px;font-size:16px}.linejump+.facebox-close{top:18px}.repo-transfer-tip{margin-bottom:0}.feed-icon{display:block;width:18px;height:18px;color:#fff;text-align:center;background:#f37538;border-radius:3px}.flash-banner{position:fixed;top:0;z-index:42;width:100%;border-top:0;border-right:0;border-left:0;border-radius:0}.signed-in-tab-flash,.signed-out-tab-flash{display:none}.stale-session-flash.is-signed-in .signed-in-tab-flash{display:inline}.stale-session-flash.is-signed-out .signed-out-tab-flash{display:inline}.site-footer{position:relative;padding-top:40px;padding-bottom:40px;margin-top:40px;font-size:12px;line-height:1.5;color:#767676;border-top:1px solid #eee}.site-footer::before{display:table;content:""}.site-footer::after{display:table;clear:both;content:""}.site-footer .octicon-mark-github{position:absolute;top:38px;left:50%;margin-left:-12px;font-size:24px;color:#ccc}.site-footer .octicon-mark-github:hover{color:#bbb}.site-footer-links{margin:0;list-style:none}.site-footer-links li{display:inline-block;line-height:16px}.site-footer-links li+li{margin-left:10px}.gollum-editor{padding:10px 0 0;margin:10px 0 50px;border:0}.gollum-editor .comment-form-head.tabnav{border:1px solid #ddd}.gollum-editor .gollum-editor-body{height:390px;margin:13px 0 5px;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;line-height:22px;resize:vertical}.gollum-editor .gollum-editor-body+.collapsed,.gollum-editor .gollum-editor-body+.expanded{margin-top:7px;border-top:1px solid #ddd}.gollum-editor .collapsed,.gollum-editor .expanded{display:block;padding:10px 0 5px;overflow:hidden;border-bottom:1px solid #ddd}.gollum-editor .collapsed a.button,.gollum-editor .expanded a.button{display:block;float:left;width:25px;height:25px;padding:0;margin:2px 5px 7px 0;overflow:hidden;color:#333;text-shadow:0 1px 0 #fff;background-image:-webkit-linear-gradient(#fafafa, #eaeaea);background-image:linear-gradient(#fafafa, #eaeaea);border:1px solid #ddd;border-radius:3px}.gollum-editor .collapsed a.button:hover,.gollum-editor .expanded a.button:hover{color:#fff;text-decoration:none;text-shadow:0 -1px 0 rgba(0,0,0,0.3);background-image:-webkit-linear-gradient(#599bdc, #3072b3);background-image:linear-gradient(#599bdc, #3072b3)}.gollum-editor .collapsed a.button span,.gollum-editor .expanded a.button span{margin:4px}.gollum-editor .collapsed h4,.gollum-editor .expanded h4{float:left;padding:6px 0 0 4px;margin:0;font-size:16px;text-shadow:0 -1px 0 #fff}.gollum-editor .collapsed a.button span.octicon-triangle-right{display:inline-block}.gollum-editor .collapsed textarea,.gollum-editor .collapsed a.button span.octicon-triangle-down{display:none}.gollum-editor .expanded a.button span.octicon-triangle-down{display:inline-block}.gollum-editor .expanded a.button span.octicon-triangle-right{display:none}.gollum-editor .expanded textarea{display:block;width:883px;height:84px;padding:6px;margin:8px 0;clear:both;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;font-size:12px;resize:vertical;border:1px solid #ddd}.gollum-editor a.gollum-minibutton,.gollum-editor a.gollum-minibutton:visited{display:block;padding:5px 12px;margin:0 0 0 9px;font-size:12px;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;cursor:pointer;background-image:-webkit-linear-gradient();background-image:linear-gradient();border:1px solid #d4d4d4;border-radius:3px}.gollum-editor a.gollum-minibutton:hover,.gollum-editor a.gollum-minibutton:visited:hover{color:#fff;text-decoration:none;text-shadow:0 -1px 0 rgba(0,0,0,0.3);background-image:-webkit-linear-gradient(#599bdc, #3072b3);background-image:linear-gradient(#599bdc, #3072b3);border-color:#518cc6 #518cc6 #2a65a0}.singleline{display:block;margin:20px 0}.singleline label{display:block;margin-bottom:6px}.gollum-editor-title-field{margin:0 0 10px;border-bottom:0}.gollum-editor-page-title{margin-top:0;font-weight:bold}.gollum-editor-page-title.ph{color:#000}.gollum-editor-function-bar{padding-bottom:10px;margin:10px 0;border:0;border-bottom:1px solid #ddd}.gollum-editor-function-bar .gollum-editor-function-buttons{display:none;float:left}.gollum-editor-function-bar.active .gollum-editor-function-buttons{display:block}.gollum-editor-function-bar .gollum-editor-format-selector{float:left;margin-left:20px}.gollum-editor-function-bar .gollum-editor-format-selector select{margin:0}.gollum-editor-function-bar .gollum-editor-format-selector label{padding:0 5px 0 0;font-size:11px;font-weight:bold;line-height:17px;color:#767676}.gollum-editor-function-buttons .btn-sm{width:30px;padding-right:0;padding-left:0;text-align:center}.gollum-editor-function-buttons .btn-sm .octicon{margin-right:0}.gollum-error-message{display:none;padding-top:12px;font-size:1.8em;color:#f33}.gollum-editor-help{padding:0;overflow:hidden;border:1px solid #ddd;border-radius:3px}.gollum-editor-help-parent,.gollum-editor-help-list{display:block;float:left;width:160px;height:170px;padding:10px 0;margin:0;overflow:auto;list-style-type:none;border-right:1px solid #eee}.gollum-editor-help-parent li,.gollum-editor-help-list li{padding:0;margin:0;font-size:12px;line-height:1.6}.gollum-editor-help-parent li a,.gollum-editor-help-list li a{display:block;padding:2px 12px;font-weight:bold;text-shadow:0 -1px 0 #fff;border:1px solid transparent;border-width:1px 0}.gollum-editor-help-parent li a:hover,.gollum-editor-help-list li a:hover{text-decoration:none;background:#fff;border-color:#f0f0f0;box-shadow:none}.gollum-editor-help-parent li a.selected,.gollum-editor-help-list li a.selected{color:#000;background:#fff;border:1px solid #eee;border-width:1px 0;border-bottom-color:#e7e7e7;box-shadow:0 1px 2px #f0f0f0}.gollum-editor-help-list{background:#fafafa}.gollum-editor-help-wrapper{height:170px;padding:10px;overflow:auto;background:#fff}.gollum-editor-help-content{padding:0;margin:0 10px 0 5px;font-size:12px;line-height:1.8}.gollum-editor-help-content p{padding:0;margin:0 0 10px}.ie .gollum-editor .singleline input{padding-top:0.25em;padding-bottom:0.75em}.gollum-footer{font-size:12px;line-height:19px}.gollum-dialog-dialog h4{padding:0 0 6px;margin:0 0 12px;font-size:16px;font-weight:bold;line-height:normal;color:#333;text-shadow:0 -1px 0 #f7f7f7;border-bottom:1px solid #ddd}.gollum-dialog-dialog-body{padding:0;margin:0;font-size:12px;line-height:16px}.gollum-dialog-dialog-body fieldset{display:block;padding:0 12px;margin:0;overflow:hidden;border:0}.gollum-dialog-dialog-body fieldset .field{padding:0;margin:0 0 18px}.gollum-dialog-dialog-body fieldset .field:last-child{margin:0 0 12px}.gollum-dialog-dialog-body fieldset label{display:block;min-width:80px;padding:0;margin:0;font-size:14px;font-weight:bold;line-height:1.6;color:#666}.gollum-dialog-dialog-body fieldset .form-control{display:block;width:100%;margin:3px 0 0}.gollum-dialog-dialog-body fieldset .code{font-family:Monaco, "Courier New", Courier, monospace}.gollum-dialog-dialog-buttons{padding:12px 0 0;margin:14px 0 0;overflow:hidden;border-top:1px solid #ddd}a.gollum-minibutton,a.gollum-minibutton:visited{float:right;width:auto;padding:4px 12px;margin:0 0 0 9px;font-size:12px;font-weight:bold;color:#333;text-shadow:0 1px 0 #fff;cursor:pointer;background-image:-webkit-linear-gradient();background-image:linear-gradient();border:1px solid #d4d4d4;border-radius:3px}a.gollum-minibutton:hover,a.gollum-minibutton:visited:hover{color:#fff;text-decoration:none;text-shadow:0 -1px 0 rgba(0,0,0,0.3);background-image:-webkit-linear-gradient(#599bdc, #3072b3);background-image:linear-gradient(#599bdc, #3072b3);border-color:#518cc6 #518cc6 #2a65a0}.wiki-wrapper .ie .gollum-editor{padding-bottom:1em}.wiki-wrapper .wiki-content .previewable-comment-form.write-selected .write-content,.wiki-wrapper .wiki-content .previewable-comment-form.preview-selected .preview-content{padding:0;margin:0}.wiki-wrapper .wiki-content .comment-body{padding:5px 0 20px}.wiki-wrapper hr{margin:25px 0 20px}.wiki-wrapper.comment-body{width:920px}include-fragment,poll-include-fragment{display:block}.input-group{display:table}.input-group .form-control{position:relative;width:100%}.input-group .form-control:focus{z-index:2}.input-group .form-control+.btn{margin-left:0}.input-group.inline{display:inline-table}.input-group .form-control,.input-group-button{display:table-cell}.input-group-button{width:1%;vertical-align:middle}.input-group .form-control:first-child,.input-group-button:first-child .btn{border-top-right-radius:0;border-bottom-right-radius:0}.input-group-button:first-child .btn{margin-right:-1px}.input-group .form-control:last-child,.input-group-button:last-child .btn{border-top-left-radius:0;border-bottom-left-radius:0}.input-group-button:last-child .btn{margin-left:-1px}.issue-list em{padding:3px;font-style:normal;font-weight:bold;background-color:rgba(255,255,140,0.5);border-radius:3px}.issue-list .title{min-height:24px;padding:0;margin:0 80px 10px 0;font-size:18px;font-weight:normal;line-height:24px;word-wrap:break-word}.issue-list .title .octicon{position:absolute;top:-4px;left:0;color:#888}.issue-list .title .closed.octicon{color:#bd2c00}.issue-list .title .open.octicon{color:#6cc644}.issue-list .title .merged.octicon{color:#6e5494}.issue-list .description{margin:0 0 10px;overflow:hidden;line-height:20px}.issue-list-meta{margin:0;list-style-type:none}.issue-list-meta::before{display:table;content:""}.issue-list-meta::after{display:table;clear:both;content:""}.issue-list-meta>li{display:inline-block;margin-right:10px}.issue-list-meta a{color:#333}.issue-list-meta .octicon{color:#888;vertical-align:bottom}.issue-list-item{position:relative;padding:0 0 20px 40px;margin:0 0 20px;border-bottom:1px solid #f1f1f1}.jcrop-holder{text-align:left;direction:ltr;touch-action:none}.jcrop-vline,.jcrop-hline{position:absolute;font-size:0;background:#fff url("/images/spinners/Jcrop.gif")}.jcrop-vline{width:1px !important;height:100%}.jcrop-vline.right{right:0}.jcrop-hline{width:100%;height:1px !important}.jcrop-hline.bottom{bottom:0}.jcrop-tracker{width:100%;height:100%;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;-webkit-tap-highlight-color:transparent;-webkit-touch-callout:none}.jcrop-handle{width:7px;height:7px;font-size:1px;background-color:#333;border:1px #eee solid}.jcrop-handle.ord-n{top:0;left:50%;margin-top:-4px;margin-left:-4px}.jcrop-handle.ord-s{bottom:0;left:50%;margin-bottom:-4px;margin-left:-4px}.jcrop-handle.ord-e{top:50%;right:0;margin-top:-4px;margin-right:-4px}.jcrop-handle.ord-w{top:50%;left:0;margin-top:-4px;margin-left:-4px}.jcrop-handle.ord-nw{top:0;left:0;margin-top:-4px;margin-left:-4px}.jcrop-handle.ord-ne{top:0;right:0;margin-top:-4px;margin-right:-4px}.jcrop-handle.ord-se{right:0;bottom:0;margin-right:-4px;margin-bottom:-4px}.jcrop-handle.ord-sw{bottom:0;left:0;margin-bottom:-4px;margin-left:-4px}.jcrop-dragbar.ord-n,.jcrop-dragbar.ord-s{width:100%;height:7px}.jcrop-dragbar.ord-e,.jcrop-dragbar.ord-w{width:7px;height:100%}.jcrop-dragbar.ord-n{margin-top:-4px}.jcrop-dragbar.ord-s{bottom:0;margin-bottom:-4px}.jcrop-dragbar.ord-e{right:0;margin-right:-4px}.jcrop-dragbar.ord-w{margin-left:-4px}.jcrop-light .jcrop-vline,.jcrop-light .jcrop-hline{background:#fff;opacity:0.7 !important}.jcrop-light .jcrop-handle{background-color:#000;border-color:#fff;border-radius:3px}.jcrop-dark .jcrop-vline,.jcrop-dark .jcrop-hline{background:#000;opacity:0.7 !important}.jcrop-dark .jcrop-handle{background-color:#fff;border-color:#000;border-radius:3px}.jcrop-holder img,img.jcrop-preview{max-width:none}kbd{display:inline-block;padding:3px 5px;font:11px Consolas, "Liberation Mono", Menlo, Courier, monospace;line-height:10px;color:#555;vertical-align:middle;background-color:#fcfcfc;border:solid 1px #ccc;border-bottom-color:#bbb;border-radius:3px;box-shadow:inset 0 -1px 0 #bbb}.badmono{font-family:sans-serif;font-weight:bold}.labels{position:relative}.label{display:inline-block;padding:3px 4px;font-size:11px;font-weight:bold;line-height:1;color:#fff;border-radius:2px;box-shadow:inset 0 -1px 0 rgba(0,0,0,0.12)}.label:hover{text-decoration:none}.label-admin{color:#666;background-color:#eee}.label-generic{margin-top:-1px;margin-bottom:-1px;font-weight:normal;color:#767676;background-color:transparent;border:1px solid #eee;box-shadow:none}.label-recommended{color:#60b044;border:1px solid #60b044}.label-neutral{background-color:#767676}.label-private{color:#4c4a42;background-color:#ffefc6}a.label-link{border:1px solid transparent}a.label-link:hover{text-decoration:none}.label-membership-pending{background-color:#c9510c}.label-active{background-color:#6cc644}.repository-lang-stats{position:relative}.repository-lang-stats ol.repository-lang-stats-numbers li{display:table-cell;width:1%;padding:10px 5px;text-align:center;white-space:nowrap;border-bottom:0}.repository-lang-stats ol.repository-lang-stats-numbers li span.percent{float:none}.repository-lang-stats ol.repository-lang-stats-numbers li>a,.repository-lang-stats ol.repository-lang-stats-numbers li>span{font-weight:bold;color:#999;text-decoration:none}.repository-lang-stats ol.repository-lang-stats-numbers li .lang{color:#333}.repository-lang-stats ol.repository-lang-stats-numbers li .language-color{display:inline-block;width:10px;height:10px;border-radius:50%}.repository-lang-stats ol.repository-lang-stats-numbers li a:hover{background:transparent}.stats-switcher-viewport{height:38px;overflow:hidden}.stats-switcher-viewport .stats-switcher-wrapper{position:relative;top:0;-webkit-transition:top 0.25s ease-in-out;transition:top 0.25s ease-in-out}.stats-switcher-viewport.is-revealing-lang-stats .stats-switcher-wrapper{top:-38px}.stats-switcher-viewport.is-revealing-overview .stats-switcher-wrapper{top:38px}.repository-lang-stats-graph{display:table;width:100%;overflow:hidden;white-space:nowrap;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;border:1px solid #ddd;border-top:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.repository-lang-stats-graph .language-color{display:table-cell;line-height:8px;text-indent:-9999px}.repository-lang-stats-graph .language-color:first-child{border-bottom-left-radius:2px}.repository-lang-stats-graph .language-color:last-child{border-bottom-right-radius:2px}body{min-width:1020px;word-wrap:break-word}.page-content{padding-top:20px}.container-sm{width:530px}.list-group-item{position:relative;display:block;padding:8px 10px 10px 40px;margin-bottom:-1px;border:1px solid #e5e5e5}.list-group-item::before{display:table;content:""}.list-group-item::after{display:table;clear:both;content:""}.list-group-item a:hover{text-decoration:none}.list-group-item:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.list-group-item:last-child{margin-bottom:0;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.list-group-item.closed{background-color:#fcfcfc}.list-group-item.selectable{padding-left:60px}.list-group-item.selected{background-color:#ffffef}.list-group-item.navigation-focus{background-color:#f5f9fc}.list-group-item .list-group-item-summary a{color:#767676}.list-group-item .list-group-item-summary a.quiet{color:#999}.list-group-item .status{position:relative;top:2px;float:right;margin-right:-9px}.list-group-item .type-icon{position:relative;top:1px;width:16px;text-align:center;vertical-align:middle}.list-group-item .assignee{float:right}.list-group-item .assignee img{display:block;border-radius:2px}.list-group-item .labels{top:-2px;display:inline-block;margin-bottom:-2px;margin-left:4px}.list-group-item-name{margin:0 60px 2px 0;font-size:15px;line-height:1.3;word-wrap:break-word}.list-group-item-name .type-icon{float:left;margin-top:1px;margin-left:-24px}.list-group-item-link{color:#333}.closed.octicon,.reverted.octicon{color:#bd2c00}.open.octicon{color:#6cc644}.merged.octicon{color:#6e5494}.list-group-item-summary{margin-top:2px}.list-group-item-summary p{margin:0 0 5px}.standalone .list-group-item-summary p{margin-bottom:0}.animated-ellipsis-container{display:inline-block;width:12px;height:12px;overflow:hidden;-webkit-transform:translateZ(0);transform:translateZ(0)}.animated-ellipsis-container>.animated-ellipsis{display:inline-block;overflow:hidden;vertical-align:bottom;-webkit-animation:ellipsis 1s infinite;animation:ellipsis 1s infinite}@-webkit-keyframes ellipsis{from{width:2px}to{width:12px}}@keyframes ellipsis{from{width:2px}to{width:12px}}.large-loading-area{padding:100px 0;text-align:center}.user-mention,.team-mention{font-weight:bold;color:#333;white-space:nowrap}.news .account-switcher{margin-bottom:20px}.news .release{margin-top:0;margin-bottom:0}.news blockquote{color:#666}.news h1{margin-bottom:0}.news .alert{position:relative;padding:0 0 1em 45px;overflow:hidden;border-top:1px solid #f1f1f1}.news .alert .commits{padding-left:40px}.news .alert .css-truncate.css-truncate-target,.news .alert .css-truncate .css-truncate-target{max-width:180px}.news .alert p{margin:0}.news .alert .markdown-body blockquote{padding:0 0 0 40px;border:0 none}.news .alert .octicon{color:#bbb}.news .alert .dashboard-event-icon{position:absolute;top:14px;left:22px;-webkit-transform:translateX(-50%);transform:translateX(-50%)}.news .alert .body{padding:1em 0 0;overflow:hidden;font-size:14px;border-bottom:0}.news .alert .time{font-size:12px;color:#bbb}.news .alert .title{padding:0;font-weight:bold}.news .alert .title .subtle{color:#bbb}.news .alert .gravatar{float:left;margin-right:0.6em;line-height:0;background-color:#fff;border-radius:3px}.news .alert .simple .title{display:inline-block;font-size:13px;font-weight:normal;color:#666}.news .alert .simple .time{display:inline-block}.news .alert .branch-link,.news .alert .pull-info{display:inline-block;padding:3px 7px;margin-top:5px;font-size:12px;color:rgba(0,0,0,0.5);background:#e8f1f6;border-radius:3px}.news .alert .branch-link em,.news .alert .pull-info em{font-style:normal;font-weight:bold}.news .alert .branch-link{position:relative;top:-2px;margin:0;font-family:Consolas, "Liberation Mono", Menlo, Courier, monospace;color:#4183c4}.news .alert .branch-link .octicon{display:none}.news .alert:first-child{border-top:0}.news .alert:first-child .body{padding-top:0}.news .alert:first-child .dashboard-event-icon{top:0}.news .github-welcome .done{color:#666;text-decoration:line-through}.news .commits li{margin-top:0.15em;list-style-type:none}.news .commits li.more{padding-top:2px;font-size:11px}.news .commits li .committer{display:none;padding-left:0.5em}.news .commits li img{margin:0 1px 0 0;vertical-align:middle;background-color:#fff;border-radius:2px}.news .commits li img.emoji{padding:0;margin:0;border:0}.news .commits li .message{display:inline-block;max-width:390px;margin-top:2px;overflow:hidden;font-size:13px;line-height:1.3;text-overflow:ellipsis;white-space:nowrap;vertical-align:top}.news div.message,.news li blockquote{display:inline;font-size:13px;color:#666}.outline-box-group{border-radius:3px}.outline-box{padding:20px;border:solid 1px #d8d8d8}.outline-box:first-child{border-top-left-radius:3px;border-top-right-radius:3px}.outline-box:last-child{border-bottom-right-radius:3px;border-bottom-left-radius:3px}.outline-box+.outline-box{border-top:0}.outline-box-highlighted{background-color:#f7fafd;border-color:#c9d6e3}.owner-select-grid{margin-left:-8px}.owner-select-grid::before{display:table;content:""}.owner-select-grid::after{display:table;clear:both;content:""}.owner-select-target{float:left;padding:10px;margin:0 10px 20px;font-weight:bold;text-align:center;cursor:pointer;background-color:#f2f2f2;border:0;border-radius:3px}.owner-select-target:hover,.owner-select-target:focus{color:#fff;background-color:#4078c0}.owner-select-target:active{color:#fff;background-color:#33609a}.owner-select-target .css-truncate-target{max-width:90px}.owner-select-target.disabled{color:#999;cursor:not-allowed}.owner-select-target.disabled .user-mention{color:#999}.owner-select-target.disabled .owner-select-avatar{opacity:0.3}.owner-select-avatar{display:block;margin-bottom:9px}.pagehead{position:relative;padding-top:20px;padding-bottom:20px;margin-bottom:20px;border-bottom:1px solid #eee}.pagehead.admin{background:url("/images/modules/pagehead/background-yellowhatch-v3.png") 0 0 repeat-x}.pagehead ul.pagehead-actions{z-index:21;float:right;margin:0}.pagehead .path-divider{margin:0 0.25em}.pagehead h1{margin-top:0;margin-bottom:0;font-size:20px;font-weight:normal;line-height:28px}.pagehead h1 .avatar{margin-top:-2px;margin-right:9px;margin-bottom:-2px}.pagehead .account-switcher{margin-top:-3px;margin-bottom:-3px}.pagehead-heading{color:inherit}.pagehead-actions>li{float:left;margin:0 10px 0 0;font-size:11px;color:#333;list-style-type:none}.pagehead-actions>li:last-child{margin-right:0}.pagehead-actions .octicon-mute{color:#c00}.pagehead-actions .select-menu{position:relative}.pagehead-actions .select-menu::before{display:table;content:""}.pagehead-actions .select-menu::after{display:table;clear:both;content:""}.pagehead-actions .select-menu-modal-holder{top:100%}.pagehead-nav{float:right;margin-bottom:-20px}.pagehead-nav-item{float:left;padding:6px 10px 21px;margin-left:20px;font-size:14px;color:#767676}.pagehead-nav-item:hover{color:#333;text-decoration:none}.pagehead-nav-item.selected{color:#333;border-bottom:2px solid #d26911}.pagehead-nav-item+.btn-outline{margin-top:-1px;margin-left:20px}.pagehead-tabs-item{float:left;padding:8px 15px 11px;color:#666;white-space:nowrap;border:solid transparent;border-width:3px 1px 1px;border-radius:3px 3px 0 0}.pagehead-tabs-item .octicon{color:#bbb;vertical-align:text-top}.pagehead-tabs-item .counter{color:#777}.pagehead-tabs-item:hover{color:#333;text-decoration:none}.pagehead-tabs-item.selected{font-weight:bold;color:#333;background-color:#fff;border-color:#d26911 #e5e5e5 transparent}.pagehead-tabs-item.selected>.octicon{color:inherit}.repohead.experiment-repo-nav{padding-bottom:0;background-color:#fafafa}.repohead .repohead-details-container{margin-bottom:20px}.repohead.mirror h1,.repohead.fork h1{height:auto;margin-top:-5px;margin-bottom:15px}.repohead h1{position:relative;float:left;max-width:635px;padding-left:18px;font-size:18px;line-height:26px;color:#666}.repohead h1.private .octicon{color:#e9dba5}.repohead h1 .octicon{position:absolute;top:0;left:0;margin-top:5px;color:#bbb}.repohead .octicon-mirror{left:-3px}.repohead .octicon-lock{top:10px}.repohead span.fork-flag,.repohead span.mirror-flag{display:block;font-size:11px;line-height:10px;white-space:nowrap}.reponav{position:relative;top:1px;margin-top:-5px}.reponav::before{display:table;content:""}.reponav::after{display:table;clear:both;content:""}.reponav-dropdown{position:relative;float:left}.reponav-dropdown.active .dropdown-menu-content{display:block}.reponav-dropdown.active .dropdown-menu{left:15px}.reponav-item{float:left;padding:7px 15px 8px;color:#666;white-space:nowrap;border:solid transparent;border-width:3px 1px 1px;border-radius:3px 3px 0 0}.reponav-item .octicon{color:#bbb}.reponav-item .counter{color:#555}.reponav-item:hover,.reponav-item:focus{color:#333;text-decoration:none}.reponav-item.selected{color:#111;background-color:#fff;border-color:#d26911 #e5e5e5 transparent}.reponav-item.selected>.octicon{color:inherit}.pagination::before{display:table;content:""}.pagination::after{display:table;clear:both;content:""}.pagination a,.pagination span,.pagination em{position:relative;float:left;padding:7px 12px;margin-left:-1px;font-size:13px;font-style:normal;font-weight:bold;color:#4078c0;white-space:nowrap;vertical-align:middle;cursor:pointer;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none;background:#fff;border:1px solid #e5e5e5}.pagination a:first-child,.pagination span:first-child,.pagination em:first-child{margin-left:0;border-top-left-radius:3px;border-bottom-left-radius:3px}.pagination a:last-child,.pagination span:last-child,.pagination em:last-child{border-top-right-radius:3px;border-bottom-right-radius:3px}.pagination a:hover,.pagination a:focus,.pagination span:hover,.pagination span:focus,.pagination em:hover,.pagination em:focus{z-index:2;text-decoration:none;background-color:#e7e7e7;border-color:#e5e5e5}.pagination .selected{z-index:3}.pagination .current,.pagination .current:hover{z-index:3;color:#fff;background-color:#4078c0;border-color:#4078c0}.pagination .gap,.pagination .disabled,.pagination .gap:hover,.pagination .disabled:hover{color:#d3d3d3;cursor:default;background-color:#fafafa}.ajax-pagination-form .ajax-pagination-btn{width:100%;padding:6px;margin-top:20px;font-weight:bold;color:#4078c0;background:#fff;border:1px solid #e5e5e5;border-radius:3px}.ajax-pagination-form .ajax-pagination-btn:hover,.ajax-pagination-form .ajax-pagination-btn:focus{background-color:#e7e7e7}.ajax-pagination-form.loading .ajax-pagination-btn{text-indent:-3000px;background-color:#eaeaea;background-image:url("/images/spinners/octocat-spinner-16px-EAF2F5.gif");background-repeat:no-repeat;background-position:center center;border-color:#c5c5c5}@media only screen and (-webkit-min-device-pixel-ratio: 2), only screen and (min--moz-device-pixel-ratio: 2), only screen and (-moz-min-device-pixel-ratio: 2), only screen and (min-device-pixel-ratio: 2), only screen and (min-resolution: 192dpi), only screen and (min-resolution: 2dppx){.ajax-pagination-form.loading .ajax-pagination-btn{background-image:url("/images/spinners/octocat-spinner-32-EAF2F5.gif");background-size:16px auto}}.paginate-container{margin-top:20px;margin-bottom:15px;text-align:center}.paginate-container .pagination{display:inline-block}.progress-bar{display:block;height:15px;overflow:hidden;background-color:#eee;border-radius:3px}.progress-bar .progress{display:block;height:100%;background-color:#6cc644}.reverse-progress-container{position:relative;height:3px;background-color:#e5e9eb;background-image:-webkit-linear-gradient(left, #599e47, #306a94, #492860, #c03d32, #d17337);background-image:linear-gradient(to right, #599e47, #306a94, #492860, #c03d32, #d17337);background-size:100% 3px}.reverse-progress-bar{position:absolute;right:0;height:100%;background-color:#e5e9eb}.progress-bar-small{height:10px}.progress-bar-inline .progress-bar{width:100%;border-color:#e5e5e5;border-style:solid;border-width:1px;border-top:0;border-radius:0}.steps{display:table;width:100%;padding:0;margin:30px auto 0;overflow:hidden;list-style:none;border:1px solid #ddd;border-radius:3px;box-shadow:0 1px 3px rgba(0,0,0,0.05)}.steps li{display:table-cell;width:33.3%;padding:10px 15px;color:#ccc;cursor:default;background-color:#fafafa;border-left:1px solid #ddd}.steps li.current{color:#333;background-color:#fff}.steps li.current .octicon{color:#4078c0}.steps li .octicon{float:left;margin-right:15px;margin-bottom:5px}.steps li .step{display:block}.steps li:first-child{border-left:0}.steps .complete{color:#767676}.steps .complete .octicon{color:#6cc644}.prose-diff .anchor{display:none}.prose-diff .show-rich-diff{color:#4183c4;text-decoration:none;cursor:pointer}.prose-diff .show-rich-diff:hover{text-decoration:underline}.prose-diff.collapsed .rich-diff-level-zero.expandable{cursor:pointer}.prose-diff.collapsed .rich-diff-level-zero.expandable .vicinity{display:block}.prose-diff.collapsed .rich-diff-level-zero.expandable .unchanged:not(.vicinity){display:none}.prose-diff.collapsed .rich-diff-level-zero.expandable .octicon{display:block;margin:20px auto;color:#d3d3d3}.prose-diff.collapsed .rich-diff-level-zero.expandable:hover .octicon{color:#000}.prose-diff.collapsed .rich-diff-level-zero.expandable:only-child::before{font-size:18px;color:#d3d3d3;content:"Sorry, no visible changes to display."}.prose-diff.collapsed .rich-diff-level-zero.expandable:only-child:hover::before{color:#000}.prose-diff.collapsed .rich-diff-level-zero.expandable>.removed,.prose-diff.collapsed .rich-diff-level-zero.expandable>del{display:none;text-decoration:none}.prose-diff .markdown-body{padding:30px;padding-left:15px}.prose-diff .markdown-body>ins{box-shadow:inset 4px 0 0 #7fcb5c}.prose-diff .markdown-body>del{text-decoration:none;box-shadow:inset 4px 0 0 #c94114}.prose-diff .markdown-body>ins,.prose-diff .markdown-body>del{display:block;border-radius:0}.prose-diff .markdown-body>ins>.rich-diff-level-zero,.prose-diff .markdown-body>ins>.rich-diff-level-one,.prose-diff .markdown-body>del>.rich-diff-level-zero,.prose-diff .markdown-body>del>.rich-diff-level-one{margin-left:15px}.prose-diff .markdown-body>ins:first-child *,.prose-diff .markdown-body>del:first-child *{margin-top:0}.prose-diff .rich-diff-level-zero.added{box-shadow:inset 4px 0 0 #7fcb5c}.prose-diff .rich-diff-level-zero.removed{box-shadow:inset 4px 0 0 #c94114}.prose-diff .rich-diff-level-zero.changed{box-shadow:inset 4px 0 0 #ffc045}.prose-diff .rich-diff-level-zero.unchanged,.prose-diff .rich-diff-level-zero.vicinity{margin-left:15px}.prose-diff .rich-diff-level-zero.added,.prose-diff .rich-diff-level-zero.removed,.prose-diff .rich-diff-level-zero.changed{display:block;border-radius:0}.prose-diff .rich-diff-level-zero.added>.rich-diff-level-one,.prose-diff .rich-diff-level-zero.removed>.rich-diff-level-one,.prose-diff .rich-diff-level-zero.changed>.rich-diff-level-one{margin-left:15px}.prose-diff .rich-diff-level-zero.added:first-child *,.prose-diff .rich-diff-level-zero.removed:first-child *,.prose-diff .rich-diff-level-zero.changed:first-child *{margin-top:0}.prose-diff :not(.changed)>:not(.github-user-ins):not(.github-user-del)>.removed,.prose-diff :not(.changed)>:not(.github-user-ins):not(.github-user-del)>del{text-decoration:none}.prose-diff .changed del,.prose-diff .changed del pre,.prose-diff .changed del code,.prose-diff .changed del>div,.prose-diff .changed .removed,.prose-diff .changed .removed pre,.prose-diff .changed .removed code,.prose-diff .changed .removed>div{color:#a33;text-decoration:line-through;background:#ffeaea}.prose-diff .changed ins,.prose-diff .changed ins code,.prose-diff .changed ins pre,.prose-diff .changed .added{background:#eaffea;border-bottom:1px solid #3cb371}.prose-diff>.markdown-body .github-user-ins{text-decoration:underline}.prose-diff>.markdown-body .github-user-del{text-decoration:line-through}.prose-diff>.markdown-body li ul.added{background:#eaffea}.prose-diff>.markdown-body li ul.removed{color:#a33;background:#ffeaea}.prose-diff>.markdown-body li ul.removed:not(.github-user-ins){text-decoration:line-through}.prose-diff>.markdown-body li.added.moved-up .octicon,.prose-diff>.markdown-body li.added.moved-down .octicon{margin-right:5px;margin-left:5px;color:#d3d3d3}.prose-diff>.markdown-body li.added.moved{background:#ffffea}.prose-diff>.markdown-body li.removed.moved{display:none}.prose-diff>.markdown-body pre{padding:10px 20px}.prose-diff>.markdown-body th.changed,.prose-diff>.markdown-body td.changed{background:#ffffea;border-left-color:#ddd}.prose-diff>.markdown-body :not(li.moved).removed{color:#a33;text-decoration:line-through;background:#ffeaea}.prose-diff>.markdown-body :not(.github-user-ins):not(li.moved).removed{text-decoration:line-through}.prose-diff>.markdown-body :not(li.moved).added,.prose-diff>.markdown-body li:not(.moved).added{background:#eaffea}.prose-diff>.markdown-body :not(.github-user-del):not(li.moved).added li:not(.moved):not(.github-user-del).added{text-decoration:none}.prose-diff>.markdown-body li:not(.moved).removed{color:#a33;background:#ffeaea}.prose-diff>.markdown-body li:not(.moved):not(.github-user-ins).removed{text-decoration:line-through}.prose-diff>.markdown-body .added,.prose-diff>.markdown-body ins+.added,.prose-diff>.markdown-body ins{border-top:0;border-bottom:0}.prose-diff>.markdown-body .added:not(.github-user-del):not(.github-user-ins),.prose-diff>.markdown-body ins+.added:not(.github-user-del):not(.github-user-ins),.prose-diff>.markdown-body ins:not(.github-user-del):not(.github-user-ins){text-decoration:none}.prose-diff>.markdown-body img.added,.prose-diff>.markdown-body img.removed{border-style:solid;border-width:1px}.prose-diff>.markdown-body ins pre:not(.github-user-del):not(.github-user-ins),.prose-diff>.markdown-body ins code:not(.github-user-del):not(.github-user-ins),.prose-diff>.markdown-body ins>div:not(.github-user-del):not(.github-user-ins){text-decoration:none}.prose-diff>.markdown-body ul>ins,.prose-diff>.markdown-body ul>del{display:block;padding:0}.prose-diff>.markdown-body .added>li,.prose-diff>.markdown-body .removed>li{margin-top:0;margin-bottom:0}span.changed_tag,em.changed_tag,strong.changed_tag,b.changed_tag,i.changed_tag,code.changed_tag{border-bottom:1px dotted #808080;border-radius:0}a.added_href,a.changed_href,span.removed_href{border-bottom:1px dotted #808080;border-radius:0}.diff-view .file-type-prose .rich-diff{display:none}.diff-view .file-type-prose.display-rich-diff .rich-diff{display:block}.diff-view .file-type-prose.display-rich-diff .file-diff{display:none}.protip{margin-top:20px;text-align:center}.protip code{padding:2px;background-color:#f4f4f4;border-radius:3px}.protip-callout{padding:8px 10px;margin-bottom:20px;color:#4c4a42;text-align:left;border:solid 1px #eee;border-radius:3px}.radio-group::before{display:table;content:""}.radio-group::after{display:table;clear:both;content:""}.radio-label{float:left;padding:8px 10px 8px 35px;margin-left:-1px;color:#333;cursor:pointer;border:1px solid #d9d9d9}:checked+.radio-label{position:relative;z-index:1;border-color:#4078c0}.radio-label .octicon{padding-right:5px}.radio-label:first-of-type{margin-left:0;border-top-left-radius:3px;border-bottom-left-radius:3px}.radio-label:last-of-type{padding-right:16px;border-top-right-radius:3px;border-bottom-right-radius:3px}.radio-input{z-index:3;float:left;margin:11px -35px 0 12px}.add-reaction-btn{opacity:0;-webkit-transition:opacity 0.1s ease-in-out;transition:opacity 0.1s ease-in-out;-webkit-transform:translateZ(0)}.add-reaction-plus-icon{margin-right:-1px}.reaction-popover-container{display:inline-block}.reaction-popover-container.active .tooltipped::before,.reaction-popover-container.active .tooltipped::after{display:none}.reaction-popover-container.active .add-reaction-btn{opacity:1}.add-reaction-popover.dropdown-menu{width:220px}.add-reaction-popover.dropdown-menu-ne{bottom:100%;left:6px;margin-bottom:3px}.reaction-popover-form .loading-spinner{display:none;float:right}.reaction-popover-form.loading .loading-spinner{display:inline}.add-reactions-options::before{display:table;content:""}.add-reactions-options::after{display:table;clear:both;content:""}.add-reactions-options-item{float:left;width:34px;line-height:29px;-webkit-transition:-webkit-transform 0.15s cubic-bezier(0.2, 0, 0.13, 2);transition:transform 0.15s cubic-bezier(0.2, 0, 0.13, 2);-webkit-transform:scale(1);transform:scale(1)}.add-reactions-options-item:hover,.add-reactions-options-item:focus{text-decoration:none;-webkit-transform:scale(1.2);transform:scale(1.2)}.add-reactions-options-item:active{background-color:#eaf3ff}.comment-reactions::before{display:table;content:""}.comment-reactions::after{display:table;clear:both;content:""}.comment-reactions .reaction-popover-container{z-index:100}.comment-reactions.has-reactions{border-top:1px solid #e5e5e5}.comment-reactions.has-reactions:hover .add-reaction-btn{opacity:1}.comment-reactions .user-has-reacted{background-color:#f2f8fa}.comment-reactions .add-reaction-btn{border-right:0}.reaction-summary-item{float:left;padding:9px 15px 7px;line-height:18px;border-right:1px solid #e5e5e5}.reaction-summary-item:hover,.reaction-summary-item:focus{text-decoration:none}.comment-reactions-options .reaction-summary-item:first-child{border-bottom-left-radius:2px}.render-container{padding:30px;line-height:0;text-align:center;background:#ddd}.render-container .render-viewer{display:none;width:100%;height:100%;border:0}.render-container .octospinner{display:none}.render-container .render-viewer-error,.render-container .render-viewer-fatal,.render-container .render-viewer-invalid{display:none}.render-container.is-render-automatic .octospinner{display:inline-block}.render-container.is-render-requested .octospinner{display:inline-block}.render-container.is-render-requested.is-render-failed .render-viewer-error{display:inline-block}.render-container.is-render-requested.is-render-failed .render-viewer,.render-container.is-render-requested.is-render-failed .render-viewer-fatal,.render-container.is-render-requested.is-render-failed .render-viewer-invalid,.render-container.is-render-requested.is-render-failed .octospinner{display:none}.render-container.is-render-requested.is-render-failed-fatal .render-viewer-fatal{display:inline-block}.render-container.is-render-requested.is-render-failed-fatal .render-viewer,.render-container.is-render-requested.is-render-failed-fatal .render-viewer-error,.render-container.is-render-requested.is-render-failed-fatal .render-viewer-invalid,.render-container.is-render-requested.is-render-failed-fatal .octospinner{display:none}.render-container.is-render-requested.is-render-failed-invalid .render-viewer-invalid{display:inline-block}.render-container.is-render-requested.is-render-failed-invalid .render-viewer,.render-container.is-render-requested.is-render-failed-invalid .render-viewer-error,.render-container.is-render-requested.is-render-failed-invalid .render-viewer-fatal,.render-container.is-render-requested.is-render-failed-invalid .octospinner{display:none}.render-container.is-render-ready.is-render-requested:not(.is-render-failed){height:500px;padding:0;background:none}.render-container.is-render-ready.is-render-requested:not(.is-render-failed) .render-viewer{display:block}.render-container.is-render-ready.is-render-requested:not(.is-render-failed) .render-viewer-error,.render-container.is-render-ready.is-render-requested:not(.is-render-failed) .render-viewer-fatal,.render-container.is-render-ready.is-render-requested:not(.is-render-failed) .octospinner{display:none}.render-notice{padding:20px 15px;font-size:14px;color:#4c4a42;background-color:#fff9ea;border-color:#dfd8c2}.labels-list .blankslate{display:none}.labels-list .table-list-header{display:block}.labels-list.is-empty .blankslate{display:block}.labels-list.is-empty .table-list-header{display:none}.labels-list-item .table-list-cell{width:100%}.labels-list-item .label{display:inline-block;height:34px;padding:0 10px;margin-right:5px;font-size:16px;font-weight:bold;line-height:34px;text-align:center;border-radius:3px;-webkit-transition:opacity 0.2s linear;transition:opacity 0.2s linear}.labels-list-item .label .octicon{margin-right:3px}.labels-list-item .label:hover{opacity:0.85}.labels-list-item.open .label,.labels-list-item.open .label-description,.labels-list-item.open .labels-list-action{display:none}.labels-list-item.open .label-delete{display:block;text-align:left}.labels-list-item.edit .label,.labels-list-item.edit .label-description,.labels-list-item.edit .labels-list-action{display:none}.labels-list-item.edit .label-edit{display:block}.label-description{padding:8px 10px;color:#767676}.label-delete-confirmation{line-height:34px}.labels-list-actions{margin-left:60px}.labels-list-action{display:block;float:left;padding:8px 10px;color:#767676}.labels-list-action .octicon{margin-right:2px}.labels-list-action .octicon-pencil{font-size:14px}.new-label{display:none;padding:10px;margin-bottom:15px;background-color:#fafafa;border:1px solid #e5e5e5;border-radius:3px}.new-label .label-edit{display:block}.new-label .label-edit::before{display:table;content:""}.new-label .label-edit::after{display:table;clear:both;content:""}.new-label-actions{float:right}.open .new-label{display:block}.label-spinner{display:none;float:left;margin-top:9px;margin-left:-35px}.label-edit::before{display:table;content:""}.label-edit::after{display:table;clear:both;content:""}.label-edit label{display:block;margin-bottom:5px}.label-edit .error{float:left;margin-top:8px;margin-left:10px;color:#f00}.label-edit.is-valid .color-editor .octicon-check{display:block}.label-edit.loading .label-spinner{display:block}.color-editor{position:relative;float:left;width:100px}.color-editor.open .label-colors{display:block}.color-editor-bg{position:absolute;left:0;z-index:10;width:20px;height:20px;margin-top:7px;margin-left:7px;cursor:pointer;border-radius:3px}.color-editor-input{width:100px;padding-left:34px;border-color:#ccc !important}.color-editor-input:focus{border-color:#51a7e8 !important}.color-editor-input:focus ~ .label-colors{display:block}.invalid-color-indicator{position:absolute;top:7px;left:7px;z-index:11;display:none;width:20px;height:20px;font-weight:bold;line-height:20px;color:#fff;text-align:center}.label-edit-name{float:left;width:40%;margin-right:10px}.label-colors{display:none;float:left;width:auto;padding-right:5px;padding-left:5px}.label-edit,.label-delete{display:none}.label-delete-form{display:inline}.label-delete-form.loading .label-delete-spinner{display:block}.label-delete-spinner{display:none;float:left;margin-top:10px;margin-right:10px}.color-chooser{display:table-row;height:25px;list-style:none}.color-chooser li{display:table-cell;width:1%}.color-chooser li:hover{position:relative;z-index:2;outline:2px solid #fff;box-shadow:0 0 5px 2px rgba(0,0,0,0.25)}.color-chooser .color-cooser-color{display:block;width:25px;height:25px;text-align:center;cursor:pointer}.repo-list{position:relative}.repo-list .participation-graph{position:absolute;right:0;bottom:0;left:0;z-index:-1}.repo-list .participation-graph.disabled{display:none}.repo-list .participation-graph .bars{position:absolute;bottom:0}.repo-list-item{position:relative;padding-top:30px;padding-bottom:30px;list-style:none;border-bottom:1px solid #eee}.repo-list-item-with-avatar{padding-left:42px}.repo-list-item-hanging-avatar{float:left;margin-left:-42px}.repo-list-name{margin:0 0 8px;font-size:20px;line-height:1.2}.repo-list-name .prefix,.repo-list-name .slash{font-weight:normal}.repo-list-name .slash{margin-right:-4px;margin-left:-4px}.repo-list-description{max-width:550px;margin-top:8px;margin-bottom:0;font-size:14px;color:#666}.repo-list-stats{float:right;margin-top:6px;font-size:12px;font-weight:bold;color:#888}.repo-list-stats .repo-list-stat-item{display:inline-block;margin-left:8px;color:#888;text-decoration:none}.repo-list-stats .repo-list-stat-item:hover{color:#4078c0}.repo-list-stats .repo-list-stat-item>.octicon{font-size:14px}.repo-list-stats .repo-list-stat-item .octicon{vertical-align:bottom}.repo-list-stats .repo-list-stat-item .language-muted-link{margin-left:3px;color:#888}.repo-list-info{display:inline-block;height:100%;margin-bottom:0;font-size:12px;color:#888;vertical-align:middle}.repo-list-info .octicon{margin-top:-3px;font-size:12px;vertical-align:middle}.repo-list-meta{display:block;margin-top:8px;margin-bottom:0;font-size:13px;color:#888}.repo-list-meta .avatar{margin-top:-2px}.repo-list-meta a:hover{text-decoration:none}.select-menu-button::after{display:inline-block;width:0;height:0;vertical-align:-2px;content:"";border:4px solid;border-right-color:transparent;border-bottom-color:transparent;border-left-color:transparent}.select-menu-button.icon-only{padding-left:7px}.select-menu-button.primary::after{border-top-color:#fff}.select-menu-button.primary::after:active{background-color:#4a993e}.select-menu-button-large::after{margin-left:10px;vertical-align:-3px;border-width:5px}.select-menu .spinner{float:left;margin:4px 0 0 -24px}.select-menu.active .select-menu-modal-holder{display:block}.select-menu.select-menu-modal-right{position:relative}.select-menu.select-menu-modal-right .select-menu-modal-holder{right:0}.select-menu .select-menu-clear-item{display:block}.select-menu .select-menu-clear-item .octicon{color:inherit}.select-menu .select-menu-clear-item+.select-menu-no-results{display:none !important}.select-menu.is-loading .select-menu-loading-overlay{display:block}.select-menu.is-loading .select-menu-modal{min-height:200px}.select-menu-loading-overlay{position:absolute;top:0;z-index:5;display:none;width:100%;height:100%;background-color:rgba(255,255,255,0.8);border:1px solid transparent;border-radius:5px}.select-menu-loading-overlay .octicon-octoface{position:absolute;top:50%;left:50%;margin:-16px 0 0 -16px}.select-menu-modal-holder{position:absolute;z-index:30;display:none}.select-menu-modal{position:relative;width:300px;margin-top:4px;margin-bottom:20px;overflow:hidden;font-size:12px;color:#666;background-color:#fff;background-clip:padding-box;border:1px solid rgba(200,200,200,0.4);border-radius:3px;box-shadow:0 3px 12px rgba(0,0,0,0.15)}.select-menu-modal-narrow{width:200px}.select-menu-header{padding:8px 10px;line-height:16px;background:#f5f5f5;border-bottom:1px solid rgba(200,200,200,0.4)}.select-menu-header .select-menu-title{font-weight:bold;color:#333;text-shadow:0 1px 0 #fff}.select-menu-header .octicon{display:block;float:right;color:#ccc;cursor:pointer}.select-menu-header .octicon:hover{color:#555}.select-menu-header:focus{outline:none}.select-menu-filters{background-color:#f8f8f8}.select-menu-text-filter{padding:10px 10px 0}.select-menu-text-filter:first-child:last-child{padding-bottom:10px;border-bottom:1px solid #ddd}.select-menu-text-filter input{display:block;width:100%;max-width:100%;padding:5px;border:1px solid #ddd;border-radius:3px}.select-menu-text-filter input::-webkit-input-placeholder{color:#aaa}.select-menu-text-filter input::-moz-placeholder{color:#aaa}.select-menu-text-filter input:-ms-input-placeholder{color:#aaa}.select-menu-text-filter input::placeholder{color:#aaa}.select-menu-tabs{padding:10px 10px 0;border-bottom:1px solid #ddd}.select-menu-tabs ul{position:relative;bottom:-1px}.select-menu-tabs .select-menu-tab{display:inline-block}.select-menu-tabs a,.select-menu-tabs .select-menu-tab-nav{display:inline-block;padding:4px 8px 2px;font-size:11px;font-weight:bold;color:#888;text-decoration:none;cursor:pointer;background:transparent;border:1px solid transparent;border-radius:3px 3px 0 0}.select-menu-tabs a:hover,.select-menu-tabs .select-menu-tab-nav:hover{color:#333}.select-menu-tabs a.selected,.select-menu-tabs .select-menu-tab-nav.selected{color:#333;background-color:#fff;border-color:#ddd;border-bottom-color:#fff}.select-menu-list{position:relative;max-height:400px;overflow:auto;line-height:1.4}.select-menu-list.select-menu-tab-bucket{display:none}.select-menu-list.select-menu-tab-bucket.selected{display:block}.select-menu-list.is-showing-new-item-form .select-menu-new-item-form{display:block}.select-menu-list.is-showing-new-item-form .select-menu-no-results,.select-menu-list.is-showing-new-item-form .select-menu-clear-item{display:none}.select-menu-blankslate{padding:16px;text-align:center}.select-menu-blankslate svg{display:block;margin-right:auto;margin-bottom:9px;margin-left:auto;fill:#929292}.select-menu-blankslate h3{font-size:14px;color:#333}.select-menu-blankslate p{width:195px;margin-right:auto;margin-bottom:0;margin-left:auto}.select-menu-item{display:block;padding:8px 8px 8px 30px;overflow:hidden;color:inherit;cursor:pointer;border-bottom:1px solid #eee}.select-menu-item .select-menu-item-text .octicon-x{display:none;float:right;margin:1px 10px 0 0;opacity:0.6}.select-menu-item:hover{text-decoration:none}.select-menu-item.select-menu-item-template{display:none}.select-menu-item.disabled,.select-menu-item.disabled.selected{color:#999;cursor:default}.select-menu-item.disabled .description,.select-menu-item.disabled.selected .description{color:#999}.select-menu-item.disabled .select-menu-item-gravatar,.select-menu-item.disabled.selected .select-menu-item-gravatar{opacity:0.5}.select-menu-item .octicon{vertical-align:middle}.select-menu-item .octicon-check{visibility:hidden}.select-menu-item input[type="radio"]{display:none}.select-menu-item.navigation-focus,.select-menu-item.navigation-focus.selected,.select-menu-item.navigation-focus.select-menu-action,.select-menu-item.navigation-focus .description-inline{color:#fff;background-color:#4078c0}.select-menu-item.navigation-focus>.octicon,.select-menu-item.navigation-focus.selected>.octicon,.select-menu-item.navigation-focus.select-menu-action>.octicon,.select-menu-item.navigation-focus .description-inline>.octicon{color:#fff}.select-menu-item.navigation-focus .text-danger,.select-menu-item.navigation-focus .description,.select-menu-item.navigation-focus.selected .text-danger,.select-menu-item.navigation-focus.selected .description,.select-menu-item.navigation-focus.select-menu-action .text-danger,.select-menu-item.navigation-focus.select-menu-action .description,.select-menu-item.navigation-focus .description-inline .text-danger,.select-menu-item.navigation-focus .description-inline .description{color:#fff}.select-menu-item.navigation-focus.disabled{color:rgba(255,255,255,0.5)}.select-menu-item.navigation-focus.disabled .description{color:rgba(255,255,255,0.6)}.select-menu-item>.octicon-dash{display:none}.select-menu-item.indeterminate>.octicon-check{display:none}.select-menu-item.indeterminate>.octicon-dash{display:block}.select-menu-item.selected{color:#333}.select-menu-item.selected .description{color:#666}.select-menu-item.selected>.octicon{color:#333}.select-menu-item.selected .octicon-check{color:inherit;visibility:visible}.select-menu-item.selected .select-menu-item-text .octicon-x{display:block;color:inherit}.select-menu[data-multiple] .select-menu-item:active{background-color:transparent !important}.select-menu-item a{color:inherit;text-decoration:none}.select-menu-item .hidden-select-button-text{display:none}.select-menu-item .css-truncate-target{max-width:100%}.select-menu-item-parent{pointer-events:none}.select-menu-item-parent:hover{cursor:default}.select-menu-item-parent.navigation-focus,.select-menu-item-parent.navigation-focus.selected{color:#333;background-color:#f2f8fa}.select-menu-item-parent.navigation-focus .octicon-check,.select-menu-item-parent.navigation-focus.selected .octicon-check{color:#333}.select-menu-item-icon{float:left;margin-left:-20px}form.select-menu-item>div:first-child{display:none !important}.select-menu-list:last-child .select-menu-item:last-child,.select-menu-item.last-visible{border-bottom:0;border-radius:0 0 3px 3px}.select-menu-action{font-weight:normal;color:#555}.select-menu-action>.octicon{color:inherit}.select-menu-action:hover{color:#4078c0}.select-menu-no-results{display:none;padding:9px;color:#767676;cursor:auto}.select-menu-list.filterable-empty .select-menu-no-results,.select-menu-no-results:only-child{display:block}.select-menu-button-gravatar,.select-menu-item-gravatar{width:20px;overflow:hidden;line-height:0}.select-menu-button-gravatar img,.select-menu-item-gravatar img{display:inline-block;width:20px;height:20px;border-radius:3px}.select-menu-item-gravatar{float:left;width:20px;height:20px;margin-right:8px;border-radius:2px}.select-menu-button-gravatar{float:left;margin-right:5px}.select-menu-item-text{display:block;text-align:left}.select-menu-item-text .description{display:block;max-width:265px;margin-top:3px;font-size:12px;color:#767676}.select-menu-item-text .description-inline{font-size:10px;color:#767676}.select-menu-item-heading{display:block;margin-top:0;margin-bottom:0;font-size:14px;font-weight:bold}.select-menu-item-heading .description{display:inline;font-weight:normal}.select-menu-new-item-form{display:none}.select-menu-new-item-form .octicon{color:#4078c0}.modal-backdrop{display:none;-webkit-user-select:none;-moz-user-select:none;-ms-user-select:none;user-select:none}body.menu-active .modal-backdrop{position:fixed;top:0;left:0;z-index:20;display:block;width:100vw;height:100vh}.shelf{padding-top:20px;margin-bottom:20px;background-color:#fefefe;border-bottom:1px solid #eee}.shelf .container{position:relative}.shelf-title{margin:0;font-size:30px;font-weight:normal}.shelf-content{width:800px;margin:50px auto;text-align:center}.shelf-lead{margin-top:10px;margin-bottom:30px;font-size:18px;color:#666}.shelf-dismiss{position:absolute;top:0;right:0;font-size:12px;color:#999}.shelf-dismiss:hover{color:#4078c0;text-decoration:none}.shelf-dismiss .close-button{width:28px;height:28px;padding:3px 5px;color:#999;border:1px solid #ddd;border-radius:28px}.shelf-cta{padding:10px 50px;font-size:16px}.intro-shelf{margin-top:0;color:#234766;background-image:-webkit-linear-gradient(270deg, rgba(255,255,255,0) 60%, #fff),-webkit-linear-gradient(20deg, #e0f1ff 32%, #fffae3);background-image:linear-gradient(180deg, rgba(255,255,255,0) 60%, #fff),linear-gradient(70deg, #e0f1ff 32%, #fffae3);border-bottom:#fff}.intro-shelf .shelf-lead{color:#5a83a5}.intro-shelf .shelf-cta{box-shadow:0 2px 12px 0 #b6d8f4}.intro-shelf .shelf-cta.btn-secondary{margin-left:20px;box-shadow:0 2px 8px 0 #e6e6e6}.orgs-help-shelf{padding-top:20px;padding-bottom:20px;margin-top:-20px;margin-bottom:20px;border-bottom:1px solid #eee}.orgs-help-shelf .orgs-help-title{font-size:30px;font-weight:normal}.orgs-help-shelf-content{width:800px;margin:50px auto;text-align:center}.orgs-help-shelf-content .orgs-help-lead{padding-right:45px;padding-left:45px;font-size:18px}.orgs-help-shelf-content .orgs-help-divider{display:block;width:150px;margin:40px auto;content:"";border-top:1px solid #ddd}.orgs-help-lead{margin-top:10px;margin-bottom:30px;color:#666}.orgs-help-items{margin-bottom:40px}.orgs-help-item-octicon{width:70px;height:70px;margin:0 auto 15px;text-align:center;background-color:#fff;border:solid 1px #e5e5e5;border-radius:50px}.orgs-help-item-octicon .octicon{margin-top:20px;color:#4078c0}.orgs-help-item-title{margin-bottom:10px;font-weight:normal}.orgs-help-item-content{margin-top:0;font-size:14px;color:#666}.orgs-help-dismiss{float:right;margin-top:5px;margin-right:10px;font-size:12px;color:#767676}.orgs-help-dismiss:hover{color:#4078c0;text-decoration:none}.orgs-help-dismiss .octicon{position:relative;top:1px}.revoke-all-repo-access,.revoke-active-repo-access{display:none}.orgs-help-title{margin-top:0;margin-bottom:0}.billing-manager-banner+.pricing-shelf{margin-top:-30px}.pricing-shelf{background-image:-webkit-linear-gradient(top, #e5e8e7, rgba(229,232,231,0) 25%);background-image:linear-gradient(to bottom, #e5e8e7, rgba(229,232,231,0) 25%)}.pricing-shelf .shelf-content{margin-top:30px}.new-pricing-org-select{position:relative;display:inline-block}.new-pricing-org-select .select-menu-modal,.new-pricing-org-select .select-menu-modal-holder{width:100%}.simple-box{padding:15px;margin-bottom:20px;background-color:#fff;border:1px solid #ddd;border-radius:3px}.simple-box-title{padding:15px;margin:-15px -15px 0;font-size:18px;border-bottom:1px solid #eee}.simple-box-footer{padding:15px;margin:15px -15px -15px;background-color:#fcfcfc;border-top:1px solid #eee;border-radius:0 0 3px 3px}.simple-stacked-bar{display:table;width:100%;height:10px;background-color:#eee}.bar-section{display:table-cell}.bar-section[style="width:0.0%"]{display:none}.bar-section-positive{background-color:#6cc644}.bar-section-negative{background-color:#bd2c00}.bar-section-alt{background-color:#6e5494}.subhead{padding-bottom:5px;margin-bottom:15px;border-bottom:1px solid #e5e5e5}.subhead-spacious{margin-top:45px}.subhead-heading{font-weight:normal}.subhead-description{margin-bottom:0;font-size:14px;color:#767676}.subhead-actions{margin-top:-4px}.facebox .sudo{padding:0}.facebox .sudo .auth-form-header{border-width:0 0 1px}.facebox .sudo .auth-form-header .mini-icon{display:none}.facebox .sudo .auth-form-body{border-width:0}.facebox .sudo+.facebox-close{padding:5px;color:#fff}.sudo-prompt,.sudo-error{display:none}.survey-container{z-index:22;width:1000px;margin:auto}.survey-box-position{padding-bottom:6px;margin-bottom:-6px}.survey-box{width:348px;background-color:#fff;border:1px solid #ddd;border-radius:3px}.survey-box-shadowed{box-shadow:0 3px 12px rgba(0,0,0,0.15)}.survey-box-header{padding:12px 18px;overflow:hidden;background-color:#444;border-top-left-radius:2px;border-top-right-radius:2px}.survey-box-title{overflow:hidden;font-weight:bold;color:#fff}.survey-box-close{padding:12px 18px;margin:-12px -18px;color:rgba(255,255,255,0.8)}.survey-box-close:hover{color:#fff}.survey-box-body{padding:12px 18px}.survey-box-lead{font-size:16px;font-weight:bold;color:#555}.survey-box-footer{padding:18px;border-top:1px solid #eee}.tab-size[data-tab-size="1"]{-moz-tab-size:1;-o-tab-size:1;tab-size:1}.tab-size[data-tab-size="2"]{-moz-tab-size:2;-o-tab-size:2;tab-size:2}.tab-size[data-tab-size="3"]{-moz-tab-size:3;-o-tab-size:3;tab-size:3}.tab-size[data-tab-size="4"]{-moz-tab-size:4;-o-tab-size:4;tab-size:4}.tab-size[data-tab-size="5"]{-moz-tab-size:5;-o-tab-size:5;tab-size:5}.tab-size[data-tab-size="6"]{-moz-tab-size:6;-o-tab-size:6;tab-size:6}.tab-size[data-tab-size="7"]{-moz-tab-size:7;-o-tab-size:7;tab-size:7}.tab-size[data-tab-size="8"]{-moz-tab-size:8;-o-tab-size:8;tab-size:8}.tab-size[data-tab-size="9"]{-moz-tab-size:9;-o-tab-size:9;tab-size:9}.tab-size[data-tab-size="10"]{-moz-tab-size:10;-o-tab-size:10;tab-size:10}.tab-size[data-tab-size="11"]{-moz-tab-size:11;-o-tab-size:11;tab-size:11}.tab-size[data-tab-size="12"]{-moz-tab-size:12;-o-tab-size:12;tab-size:12}.tag-input-container{position:relative}.tag-input-container .suggester{position:absolute;z-index:100;width:100%;margin-top:-1px}.tag-input-container ul{list-style:none}.tag-input{width:100%;padding:0 5px 3px;margin-top:5px;overflow:auto}.tag-input input{float:left;padding-left:2px;margin:0;margin-top:3px;background:none;border:0;box-shadow:none}.tag-input input:focus{box-shadow:none}.tag-input .tag-input-tag{float:left;margin:5px;margin-bottom:0;margin-left:0}.tag-input-tag{position:relative;padding:6px 30px 6px 10px;background:#eee;border-radius:3px}.tag-input-tag .remove{position:absolute;top:6px;right:6px;display:block;width:18px;height:18px;font-size:15px;line-height:16px;color:#fff;text-align:center;text-decoration:none;cursor:pointer;background:#c8c8c8;border-radius:18px}.tag-input-tag .remove:hover{background:#bd2c00}.task-list-item{list-style-type:none}.task-list-item label{font-weight:normal}.task-list-item.enabled label{cursor:pointer}.task-list-item+.task-list-item{margin-top:3px}.task-list-item .handle{display:none}.task-list-item-checkbox{margin:0 0.2em 0.25em -1.6em;vertical-align:middle}.reorderable-task-lists .markdown-body .contains-task-list{padding:0}.reorderable-task-lists .markdown-body li:not(.task-list-item){margin-left:26px}.reorderable-task-lists .markdown-body ol:not(.contains-task-list) li,.reorderable-task-lists .markdown-body ul:not(.contains-task-list) li{margin-left:0}.reorderable-task-lists .markdown-body li p{margin-top:0}.reorderable-task-lists .markdown-body .task-list-item{padding-right:15px;padding-left:42px;margin-right:-15px;margin-left:-15px;border:1px solid transparent}.reorderable-task-lists .markdown-body .task-list-item+.task-list-item{margin-top:0}.reorderable-task-lists .markdown-body .task-list-item .contains-task-list{padding-top:4px}.reorderable-task-lists .markdown-body .task-list-item .handle{display:block;float:left;width:20px;padding:2px 0 0 2px;margin-left:-43px;opacity:0}.reorderable-task-lists .markdown-body .task-list-item .drag-handle{fill:#333}.reorderable-task-lists .markdown-body .task-list-item.hovered{background:#fafafa;border-top-color:#ededed;border-bottom-color:#ededed}.reorderable-task-lists .markdown-body .task-list-item.hovered>.handle{opacity:1}.reorderable-task-lists .markdown-body .task-list-item.is-dragging{opacity:0}.reorderable-task-lists .markdown-body .task-list-item.is-ghost{border-right-color:#ededed;border-left-color:#ededed}.team-grid{position:relative;margin-right:-10px;margin-left:-10px}.team-grid::before{display:table;content:""}.team-grid::after{display:table;clear:both;content:""}.team-grid .team{position:relative;float:left;width:480px;padding:15px;margin-right:10px;margin-bottom:20px;margin-left:10px;border:1px solid #eee;border-radius:3px}.team-grid .team-name{display:block;margin-top:-2px;font-size:18px;font-weight:bold;color:#333;text-decoration:none}.team-grid .team-name .css-truncate-target{max-width:315px}.team-grid .team-name:focus,.team-grid .team-name:hover{color:#4078c0}.team-grid .team-name:focus{outline:none}.team-grid .team-description{max-width:80%;margin-top:5px;font-size:14px;color:#767676;text-overflow:ellipsis;white-space:nowrap}.team-grid .team-description .label-private{text-transform:uppercase}.team-grid .team-label-ldap{float:right}.team-grid .team-members{width:478px;padding:10px 15px;margin:0 -15px -15px;background-color:#f8f8f8;border-top:1px solid #eee;border-radius:0 0 3px 3px}.team-grid .team-members .btn-sm{margin-top:2px;margin-bottom:2px}.team-grid .team-member{display:inline-block;width:30px;height:30px;vertical-align:top}.team-grid .team-member:hover{text-decoration:none}.team-grid .blankslate{margin-right:10px;margin-left:10px}.team-grid .team-actions-form{float:right}.team-label-ldap{display:inline-block;padding:0 9px;line-height:25px;color:#767676;text-transform:uppercase;cursor:default;border:1px solid #eaeaea;border-radius:3px;box-shadow:none}.team-label-ldap.header-label-ldap{padding:3px 5px}.team-member-ellipsis{display:inline-block;width:30px;height:30px;font-weight:bold;line-height:24px;color:#767676;text-align:center;vertical-align:top;background-color:#ddd;border-radius:3px}.team-member-ellipsis:hover{color:#333;text-decoration:none}.toolbar-commenting{float:right;margin-top:7px}.toolbar-commenting .dropdown.active .dropdown-menu-content{display:block}.toolbar-commenting .dropdown-menu-s{width:100px}.toolbar-commenting .dropdown-item{font-weight:bold;line-height:1em;background:none;border:0}.toolbar-commenting .dropdown-item:hover{color:#4078c0}.toolbar-commenting .dropdown-item:focus{color:#4078c0;outline:none}.toolbar-item{display:block;float:left;padding:4px 5px;color:#767676;background:none;border:0}.toolbar-item.dropdown,.toolbar-item.select-menu{padding:0}.toolbar-item .select-menu-modal{margin-top:2px}.toolbar-item .select-menu-item{padding-left:8px}.toolbar-item .menu-target{display:block;padding:4px 5px;color:#767676;background:none;border:0}.toolbar-item .menu-target:hover,.toolbar-item:hover{color:#4078c0}.toolbar-item .menu-target:focus,.toolbar-item:focus{color:#4078c0;outline:none}.toolbar-item .dropdown-caret{display:block;float:right;margin-top:6px;margin-left:0}.toolbar-item:disabled{color:#ddd}.toolbar-item .octicon-link,.toolbar-item .octicon-tasklist{margin-left:-3px}.toolbar-item .octicon-mention{margin-left:-4px}.toolbar-item .octicon-bold{margin-left:-2px}.toolbar-group{display:inline-block;margin-left:20px}.toolbar-group:first-child{margin-left:0}.toolbar-help{float:left;margin-top:-2px;margin-bottom:10px;margin-left:1px}.toolbar-help .octicon{vertical-align:bottom}.dropdown-header1{font-size:20px}.dropdown-header2{font-size:18px}.dropdown-header3{font-size:14px}.typeahead-result{position:relative;display:block;min-width:100%;padding:10px;margin-top:0;color:#333;cursor:pointer}.typeahead-result::before{display:table;content:""}.typeahead-result::after{display:table;clear:both;content:""}.typeahead-result:first-child{border-top:0}.typeahead-result:focus,.typeahead-result:hover,.typeahead-result.navigation-focus{text-decoration:none}.typeahead-result:hover,.typeahead-result.navigation-focus{color:#fff;background-color:#4078c0}.typeahead-result:hover .octicon-plus,.typeahead-result.navigation-focus .octicon-plus{color:#fff}.member-suggestion{padding-left:44px}.member-suggestion .avatar{float:left;margin-right:10px;margin-left:-34px}.member-suggestion .member-suggestion-info{width:75%;margin-top:2px;margin-bottom:0;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.member-suggestion .member-name{font-size:12px;color:#767676}.member-suggestion .member-email{margin-top:0;margin-bottom:0}.member-suggestion .octicon-plus,.member-suggestion .octicon-check{position:absolute;top:50%;right:15px;margin-top:-8px;color:#ddd}.member-suggestion .already-member-note,.member-suggestion .non-member-note,.member-suggestion .non-member-action{margin-top:0;margin-bottom:0}.member-suggestion .non-member-action{display:none}.member-suggestion:hover .member-name,.member-suggestion:hover .non-member-note,.member-suggestion:hover .already-member-note,.member-suggestion:hover .non-member-action,.member-suggestion:hover .member-email,.member-suggestion.navigation-focus .member-name,.member-suggestion.navigation-focus .non-member-note,.member-suggestion.navigation-focus .already-member-note,.member-suggestion.navigation-focus .non-member-action,.member-suggestion.navigation-focus .member-email{color:#fff}.member-suggestion:hover .non-member-note,.member-suggestion.navigation-focus .non-member-note{display:none}.member-suggestion:hover .non-member-action,.member-suggestion.navigation-focus .non-member-action{display:block}.member-suggestion:hover .octicon-plus,.member-suggestion:hover .octicon-check,.member-suggestion.navigation-focus .octicon-plus,.member-suggestion.navigation-focus .octicon-check{color:#fff}.member-suggestion.not-a-member .member-info,.member-suggestion.disabled .member-info{margin-top:-2px}.member-suggestion.disabled{opacity:0.5}.non-member-result{margin-left:19px}.non-member-result.disabled{pointer-events:none;opacity:0.5}.team-suggestion{padding-left:32px}.team-suggestion .octicon{float:left;margin-top:2px;margin-left:-22px}.team-suggestion .team-suggestion-info{margin:2px 0 0}.team-suggestion .team-suggestion-info .css-truncate-target{max-width:none}.team-suggestion .team-size,.team-suggestion .team-description{font-size:12px;color:#767676}.team-suggestion.navigation-focus .team-size,.team-suggestion.navigation-focus .team-description{color:#fff}.repo-access-add-team .team-name{font-size:13px}.repo-access-add-team .team-description{display:block}.repo-access-add-team .team-size,.repo-access-add-team .team-description{font-size:12px;color:#767676}.repo-access-add-team.navigation-focus .team-size,.repo-access-add-team.navigation-focus .team-description{color:#fff}#user-content-toc{overflow:visible}#user-content-toc tr{border-top:0}#user-content-toc td{padding:0 20px;background-color:#f7f7f7;border:0;border-radius:3px}#user-content-toc ul{padding-left:0;font-weight:bold;list-style:none}#user-content-toc ul li{padding-left:0.2em}#user-content-toc ul ul{font-weight:normal}#user-content-toc ul ul li::before{float:left;margin-top:-0.2em;margin-right:0.2em;font-size:1.2em;line-height:1;color:#aaa;content:"\231e"}#user-content-toc ul ul ul{padding-left:0.9em}#user-content-toctitle h2{margin-top:1em;margin-bottom:0.5em;font-size:1.25em;border-bottom:0}.user-list em{padding:3px;font-style:normal;font-weight:bold;background-color:rgba(255,255,140,0.5);border-radius:3px}.user-list .avatar{position:absolute;top:0;left:0}.user-list-info{min-height:48px;padding:0;font-size:18px;font-weight:normal;line-height:20px}.user-list-bio{margin:8px 0 0}.user-list-meta{margin:8px 0 0;overflow:hidden;list-style-type:none}.user-list-meta>li{float:left;margin-right:10px}.user-list-meta a{color:#333}.user-list-meta .octicon{vertical-align:text-bottom}.user-list-item{position:relative;padding:0 0 20px 58px;margin:0 0 20px;border-bottom:1px solid #f1f1f1}.follow-list{list-style-type:none}.follow-list .follow-list-item{width:305px;height:100px;padding-bottom:20px;margin-right:20px;margin-bottom:20px}.follow-list .follower-list-align-top{vertical-align:top}.follow-list .flagged-banner{width:75px;padding:3px 0;font-size:10px;font-weight:bold;color:#fff;text-transform:uppercase;background-color:#bd2c00;border-bottom-right-radius:3px;border-bottom-left-radius:3px}.follow-list .follow-list-name{margin-bottom:1px;font-weight:normal}.follow-list .follow-list-name a{color:inherit}.follow-list .follow-list-info{margin-bottom:0.6em;font-size:12px;color:#767676}.follow-list .css-truncate.css-truncate-target{max-width:190px}.user-select-contain{-ms-user-select:element;-webkit-user-select:contain;-moz-user-select:contain;user-select:contain}.wiki-list em{padding:3px;font-style:normal;font-weight:bold;background-color:rgba(255,255,140,0.5);border-radius:3px}.wiki-list .avatar{float:left}.wiki-list .title{min-height:24px;margin:-3px 0 10px 38px;font-weight:bold;line-height:1.2}.wiki-list .title .updated-at{font-weight:normal}.wiki-list .repo-specific .title,.wiki-list .repo-specific .full-path{margin-left:0}.wiki-list .description{margin:0 0 10px;overflow:hidden;line-height:20px}.wiki-list .wiki-list-item+.wiki-list-item{padding-top:20px;margin-top:20px;margin-bottom:10px;border-top:1px solid #eee}.zeroclipboard-link{padding:0;margin:0;color:#4078c0;cursor:pointer;background:none;border:0}.zeroclipboard-link .octicon{display:block} + diff --git a/src/main/resources/help_html/index.html b/src/main/resources/help_html/index.html new file mode 100644 index 000000000000..1f4a56dc7366 --- /dev/null +++ b/src/main/resources/help_html/index.html @@ -0,0 +1,182 @@ + + + + + + Bootstrap 3 Affix Sidebar + + + + + + + +

+ +
+
+
+
+

AddressBook +

User Guide

+

+
+
+
+
+ + +
+
+ + + + +
+

WHY do we have this?

+

This is a sample app that is used for training and experimentation of HubTurbo developers. It tries to simulate various situations we have in HT, but at a smaller scale.

+ +
+ +

WHAT does it do?

+

+ It is an address book application. Data are stored in an XML file. It periodically syncs data in the primary XML file with another mirror file. +

+ +
+ +

Keyboard Shortcuts

+
+ +
+

Note: On Mac OS, use in place of Ctrl

+
+ +

Shortcuts

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ShortcutAction
AtAg the selected person
DDelete the selected person
EEdit the selected person
G,BGo to Bottom of the list
G,TGo to Top of the list
Ctrl + NNew file
Ctrl + OOpen file
Ctrl + SSave file
Ctrl + Alt + SSave as ...
Ctrl + DownJump from filter box to person list
Ctrl + NumberJump to Nth item in the person list, N=1..9
+ +

Hotkeys

+ +

Unlike keyboard shortcuts given above, hotkeys work even when the app is not in focus.

+ + + + + + + + + + + + + + + +
ShortcutAction
Ctrl + Alt + XMinimize app
Ctrl + Shift + XToggle between maximum and default app Window sizes
+
+ +
+ +
+
+
+ + + + + + + + + \ No newline at end of file diff --git a/src/main/resources/help_html/js/bootstrap.min.js b/src/main/resources/help_html/js/bootstrap.min.js new file mode 100644 index 000000000000..b04a0e82fffe --- /dev/null +++ b/src/main/resources/help_html/js/bootstrap.min.js @@ -0,0 +1,6 @@ +/*! + * Bootstrap v3.1.1 (http://getbootstrap.com) + * Copyright 2011-2014 Twitter, Inc. + * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE) + */ +if("undefined"==typeof jQuery)throw new Error("Bootstrap's JavaScript requires jQuery");+function(a){"use strict";function b(){var a=document.createElement("bootstrap"),b={WebkitTransition:"webkitTransitionEnd",MozTransition:"transitionend",OTransition:"oTransitionEnd otransitionend",transition:"transitionend"};for(var c in b)if(void 0!==a.style[c])return{end:b[c]};return!1}a.fn.emulateTransitionEnd=function(b){var c=!1,d=this;a(this).one(a.support.transition.end,function(){c=!0});var e=function(){c||a(d).trigger(a.support.transition.end)};return setTimeout(e,b),this},a(function(){a.support.transition=b()})}(jQuery),+function(a){"use strict";var b='[data-dismiss="alert"]',c=function(c){a(c).on("click",b,this.close)};c.prototype.close=function(b){function c(){f.trigger("closed.bs.alert").remove()}var d=a(this),e=d.attr("data-target");e||(e=d.attr("href"),e=e&&e.replace(/.*(?=#[^\s]*$)/,""));var f=a(e);b&&b.preventDefault(),f.length||(f=d.hasClass("alert")?d:d.parent()),f.trigger(b=a.Event("close.bs.alert")),b.isDefaultPrevented()||(f.removeClass("in"),a.support.transition&&f.hasClass("fade")?f.one(a.support.transition.end,c).emulateTransitionEnd(150):c())};var d=a.fn.alert;a.fn.alert=function(b){return this.each(function(){var d=a(this),e=d.data("bs.alert");e||d.data("bs.alert",e=new c(this)),"string"==typeof b&&e[b].call(d)})},a.fn.alert.Constructor=c,a.fn.alert.noConflict=function(){return a.fn.alert=d,this},a(document).on("click.bs.alert.data-api",b,c.prototype.close)}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.isLoading=!1};b.DEFAULTS={loadingText:"loading..."},b.prototype.setState=function(b){var c="disabled",d=this.$element,e=d.is("input")?"val":"html",f=d.data();b+="Text",f.resetText||d.data("resetText",d[e]()),d[e](f[b]||this.options[b]),setTimeout(a.proxy(function(){"loadingText"==b?(this.isLoading=!0,d.addClass(c).attr(c,c)):this.isLoading&&(this.isLoading=!1,d.removeClass(c).removeAttr(c))},this),0)},b.prototype.toggle=function(){var a=!0,b=this.$element.closest('[data-toggle="buttons"]');if(b.length){var c=this.$element.find("input");"radio"==c.prop("type")&&(c.prop("checked")&&this.$element.hasClass("active")?a=!1:b.find(".active").removeClass("active")),a&&c.prop("checked",!this.$element.hasClass("active")).trigger("change")}a&&this.$element.toggleClass("active")};var c=a.fn.button;a.fn.button=function(c){return this.each(function(){var d=a(this),e=d.data("bs.button"),f="object"==typeof c&&c;e||d.data("bs.button",e=new b(this,f)),"toggle"==c?e.toggle():c&&e.setState(c)})},a.fn.button.Constructor=b,a.fn.button.noConflict=function(){return a.fn.button=c,this},a(document).on("click.bs.button.data-api","[data-toggle^=button]",function(b){var c=a(b.target);c.hasClass("btn")||(c=c.closest(".btn")),c.button("toggle"),b.preventDefault()})}(jQuery),+function(a){"use strict";var b=function(b,c){this.$element=a(b),this.$indicators=this.$element.find(".carousel-indicators"),this.options=c,this.paused=this.sliding=this.interval=this.$active=this.$items=null,"hover"==this.options.pause&&this.$element.on("mouseenter",a.proxy(this.pause,this)).on("mouseleave",a.proxy(this.cycle,this))};b.DEFAULTS={interval:5e3,pause:"hover",wrap:!0},b.prototype.cycle=function(b){return b||(this.paused=!1),this.interval&&clearInterval(this.interval),this.options.interval&&!this.paused&&(this.interval=setInterval(a.proxy(this.next,this),this.options.interval)),this},b.prototype.getActiveIndex=function(){return this.$active=this.$element.find(".item.active"),this.$items=this.$active.parent().children(),this.$items.index(this.$active)},b.prototype.to=function(b){var c=this,d=this.getActiveIndex();return b>this.$items.length-1||0>b?void 0:this.sliding?this.$element.one("slid.bs.carousel",function(){c.to(b)}):d==b?this.pause().cycle():this.slide(b>d?"next":"prev",a(this.$items[b]))},b.prototype.pause=function(b){return b||(this.paused=!0),this.$element.find(".next, .prev").length&&a.support.transition&&(this.$element.trigger(a.support.transition.end),this.cycle(!0)),this.interval=clearInterval(this.interval),this},b.prototype.next=function(){return this.sliding?void 0:this.slide("next")},b.prototype.prev=function(){return this.sliding?void 0:this.slide("prev")},b.prototype.slide=function(b,c){var d=this.$element.find(".item.active"),e=c||d[b](),f=this.interval,g="next"==b?"left":"right",h="next"==b?"first":"last",i=this;if(!e.length){if(!this.options.wrap)return;e=this.$element.find(".item")[h]()}if(e.hasClass("active"))return this.sliding=!1;var j=a.Event("slide.bs.carousel",{relatedTarget:e[0],direction:g});return this.$element.trigger(j),j.isDefaultPrevented()?void 0:(this.sliding=!0,f&&this.pause(),this.$indicators.length&&(this.$indicators.find(".active").removeClass("active"),this.$element.one("slid.bs.carousel",function(){var b=a(i.$indicators.children()[i.getActiveIndex()]);b&&b.addClass("active")})),a.support.transition&&this.$element.hasClass("slide")?(e.addClass(b),e[0].offsetWidth,d.addClass(g),e.addClass(g),d.one(a.support.transition.end,function(){e.removeClass([b,g].join(" ")).addClass("active"),d.removeClass(["active",g].join(" ")),i.sliding=!1,setTimeout(function(){i.$element.trigger("slid.bs.carousel")},0)}).emulateTransitionEnd(1e3*d.css("transition-duration").slice(0,-1))):(d.removeClass("active"),e.addClass("active"),this.sliding=!1,this.$element.trigger("slid.bs.carousel")),f&&this.cycle(),this)};var c=a.fn.carousel;a.fn.carousel=function(c){return this.each(function(){var d=a(this),e=d.data("bs.carousel"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c),g="string"==typeof c?c:f.slide;e||d.data("bs.carousel",e=new b(this,f)),"number"==typeof c?e.to(c):g?e[g]():f.interval&&e.pause().cycle()})},a.fn.carousel.Constructor=b,a.fn.carousel.noConflict=function(){return a.fn.carousel=c,this},a(document).on("click.bs.carousel.data-api","[data-slide], [data-slide-to]",function(b){var c,d=a(this),e=a(d.attr("data-target")||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,"")),f=a.extend({},e.data(),d.data()),g=d.attr("data-slide-to");g&&(f.interval=!1),e.carousel(f),(g=d.attr("data-slide-to"))&&e.data("bs.carousel").to(g),b.preventDefault()}),a(window).on("load",function(){a('[data-ride="carousel"]').each(function(){var b=a(this);b.carousel(b.data())})})}(jQuery),+function(a){"use strict";var b=function(c,d){this.$element=a(c),this.options=a.extend({},b.DEFAULTS,d),this.transitioning=null,this.options.parent&&(this.$parent=a(this.options.parent)),this.options.toggle&&this.toggle()};b.DEFAULTS={toggle:!0},b.prototype.dimension=function(){var a=this.$element.hasClass("width");return a?"width":"height"},b.prototype.show=function(){if(!this.transitioning&&!this.$element.hasClass("in")){var b=a.Event("show.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.$parent&&this.$parent.find("> .panel > .in");if(c&&c.length){var d=c.data("bs.collapse");if(d&&d.transitioning)return;c.collapse("hide"),d||c.data("bs.collapse",null)}var e=this.dimension();this.$element.removeClass("collapse").addClass("collapsing")[e](0),this.transitioning=1;var f=function(){this.$element.removeClass("collapsing").addClass("collapse in")[e]("auto"),this.transitioning=0,this.$element.trigger("shown.bs.collapse")};if(!a.support.transition)return f.call(this);var g=a.camelCase(["scroll",e].join("-"));this.$element.one(a.support.transition.end,a.proxy(f,this)).emulateTransitionEnd(350)[e](this.$element[0][g])}}},b.prototype.hide=function(){if(!this.transitioning&&this.$element.hasClass("in")){var b=a.Event("hide.bs.collapse");if(this.$element.trigger(b),!b.isDefaultPrevented()){var c=this.dimension();this.$element[c](this.$element[c]())[0].offsetHeight,this.$element.addClass("collapsing").removeClass("collapse").removeClass("in"),this.transitioning=1;var d=function(){this.transitioning=0,this.$element.trigger("hidden.bs.collapse").removeClass("collapsing").addClass("collapse")};return a.support.transition?void this.$element[c](0).one(a.support.transition.end,a.proxy(d,this)).emulateTransitionEnd(350):d.call(this)}}},b.prototype.toggle=function(){this[this.$element.hasClass("in")?"hide":"show"]()};var c=a.fn.collapse;a.fn.collapse=function(c){return this.each(function(){var d=a(this),e=d.data("bs.collapse"),f=a.extend({},b.DEFAULTS,d.data(),"object"==typeof c&&c);!e&&f.toggle&&"show"==c&&(c=!c),e||d.data("bs.collapse",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.collapse.Constructor=b,a.fn.collapse.noConflict=function(){return a.fn.collapse=c,this},a(document).on("click.bs.collapse.data-api","[data-toggle=collapse]",function(b){var c,d=a(this),e=d.attr("data-target")||b.preventDefault()||(c=d.attr("href"))&&c.replace(/.*(?=#[^\s]+$)/,""),f=a(e),g=f.data("bs.collapse"),h=g?"toggle":d.data(),i=d.attr("data-parent"),j=i&&a(i);g&&g.transitioning||(j&&j.find('[data-toggle=collapse][data-parent="'+i+'"]').not(d).addClass("collapsed"),d[f.hasClass("in")?"addClass":"removeClass"]("collapsed")),f.collapse(h)})}(jQuery),+function(a){"use strict";function b(b){a(d).remove(),a(e).each(function(){var d=c(a(this)),e={relatedTarget:this};d.hasClass("open")&&(d.trigger(b=a.Event("hide.bs.dropdown",e)),b.isDefaultPrevented()||d.removeClass("open").trigger("hidden.bs.dropdown",e))})}function c(b){var c=b.attr("data-target");c||(c=b.attr("href"),c=c&&/#[A-Za-z]/.test(c)&&c.replace(/.*(?=#[^\s]*$)/,""));var d=c&&a(c);return d&&d.length?d:b.parent()}var d=".dropdown-backdrop",e="[data-toggle=dropdown]",f=function(b){a(b).on("click.bs.dropdown",this.toggle)};f.prototype.toggle=function(d){var e=a(this);if(!e.is(".disabled, :disabled")){var f=c(e),g=f.hasClass("open");if(b(),!g){"ontouchstart"in document.documentElement&&!f.closest(".navbar-nav").length&&a(''}),b.prototype=a.extend({},a.fn.tooltip.Constructor.prototype),b.prototype.constructor=b,b.prototype.getDefaults=function(){return b.DEFAULTS},b.prototype.setContent=function(){var a=this.tip(),b=this.getTitle(),c=this.getContent();a.find(".popover-title")[this.options.html?"html":"text"](b),a.find(".popover-content")[this.options.html?"string"==typeof c?"html":"append":"text"](c),a.removeClass("fade top bottom left right in"),a.find(".popover-title").html()||a.find(".popover-title").hide()},b.prototype.hasContent=function(){return this.getTitle()||this.getContent()},b.prototype.getContent=function(){var a=this.$element,b=this.options;return a.attr("data-content")||("function"==typeof b.content?b.content.call(a[0]):b.content)},b.prototype.arrow=function(){return this.$arrow=this.$arrow||this.tip().find(".arrow")},b.prototype.tip=function(){return this.$tip||(this.$tip=a(this.options.template)),this.$tip};var c=a.fn.popover;a.fn.popover=function(c){return this.each(function(){var d=a(this),e=d.data("bs.popover"),f="object"==typeof c&&c;(e||"destroy"!=c)&&(e||d.data("bs.popover",e=new b(this,f)),"string"==typeof c&&e[c]())})},a.fn.popover.Constructor=b,a.fn.popover.noConflict=function(){return a.fn.popover=c,this}}(jQuery),+function(a){"use strict";function b(c,d){var e,f=a.proxy(this.process,this);this.$element=a(a(c).is("body")?window:c),this.$body=a("body"),this.$scrollElement=this.$element.on("scroll.bs.scroll-spy.data-api",f),this.options=a.extend({},b.DEFAULTS,d),this.selector=(this.options.target||(e=a(c).attr("href"))&&e.replace(/.*(?=#[^\s]+$)/,"")||"")+" .nav li > a",this.offsets=a([]),this.targets=a([]),this.activeTarget=null,this.refresh(),this.process()}b.DEFAULTS={offset:10},b.prototype.refresh=function(){var b=this.$element[0]==window?"offset":"position";this.offsets=a([]),this.targets=a([]);{var c=this;this.$body.find(this.selector).map(function(){var d=a(this),e=d.data("target")||d.attr("href"),f=/^#./.test(e)&&a(e);return f&&f.length&&f.is(":visible")&&[[f[b]().top+(!a.isWindow(c.$scrollElement.get(0))&&c.$scrollElement.scrollTop()),e]]||null}).sort(function(a,b){return a[0]-b[0]}).each(function(){c.offsets.push(this[0]),c.targets.push(this[1])})}},b.prototype.process=function(){var a,b=this.$scrollElement.scrollTop()+this.options.offset,c=this.$scrollElement[0].scrollHeight||this.$body[0].scrollHeight,d=c-this.$scrollElement.height(),e=this.offsets,f=this.targets,g=this.activeTarget;if(b>=d)return g!=(a=f.last()[0])&&this.activate(a);if(g&&b<=e[0])return g!=(a=f[0])&&this.activate(a);for(a=e.length;a--;)g!=f[a]&&b>=e[a]&&(!e[a+1]||b<=e[a+1])&&this.activate(f[a])},b.prototype.activate=function(b){this.activeTarget=b,a(this.selector).parentsUntil(this.options.target,".active").removeClass("active");var c=this.selector+'[data-target="'+b+'"],'+this.selector+'[href="'+b+'"]',d=a(c).parents("li").addClass("active");d.parent(".dropdown-menu").length&&(d=d.closest("li.dropdown").addClass("active")),d.trigger("activate.bs.scrollspy")};var c=a.fn.scrollspy;a.fn.scrollspy=function(c){return this.each(function(){var d=a(this),e=d.data("bs.scrollspy"),f="object"==typeof c&&c;e||d.data("bs.scrollspy",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.scrollspy.Constructor=b,a.fn.scrollspy.noConflict=function(){return a.fn.scrollspy=c,this},a(window).on("load",function(){a('[data-spy="scroll"]').each(function(){var b=a(this);b.scrollspy(b.data())})})}(jQuery),+function(a){"use strict";var b=function(b){this.element=a(b)};b.prototype.show=function(){var b=this.element,c=b.closest("ul:not(.dropdown-menu)"),d=b.data("target");if(d||(d=b.attr("href"),d=d&&d.replace(/.*(?=#[^\s]*$)/,"")),!b.parent("li").hasClass("active")){var e=c.find(".active:last a")[0],f=a.Event("show.bs.tab",{relatedTarget:e});if(b.trigger(f),!f.isDefaultPrevented()){var g=a(d);this.activate(b.parent("li"),c),this.activate(g,g.parent(),function(){b.trigger({type:"shown.bs.tab",relatedTarget:e})})}}},b.prototype.activate=function(b,c,d){function e(){f.removeClass("active").find("> .dropdown-menu > .active").removeClass("active"),b.addClass("active"),g?(b[0].offsetWidth,b.addClass("in")):b.removeClass("fade"),b.parent(".dropdown-menu")&&b.closest("li.dropdown").addClass("active"),d&&d()}var f=c.find("> .active"),g=d&&a.support.transition&&f.hasClass("fade");g?f.one(a.support.transition.end,e).emulateTransitionEnd(150):e(),f.removeClass("in")};var c=a.fn.tab;a.fn.tab=function(c){return this.each(function(){var d=a(this),e=d.data("bs.tab");e||d.data("bs.tab",e=new b(this)),"string"==typeof c&&e[c]()})},a.fn.tab.Constructor=b,a.fn.tab.noConflict=function(){return a.fn.tab=c,this},a(document).on("click.bs.tab.data-api",'[data-toggle="tab"], [data-toggle="pill"]',function(b){b.preventDefault(),a(this).tab("show")})}(jQuery),+function(a){"use strict";var b=function(c,d){this.options=a.extend({},b.DEFAULTS,d),this.$window=a(window).on("scroll.bs.affix.data-api",a.proxy(this.checkPosition,this)).on("click.bs.affix.data-api",a.proxy(this.checkPositionWithEventLoop,this)),this.$element=a(c),this.affixed=this.unpin=this.pinnedOffset=null,this.checkPosition()};b.RESET="affix affix-top affix-bottom",b.DEFAULTS={offset:0},b.prototype.getPinnedOffset=function(){if(this.pinnedOffset)return this.pinnedOffset;this.$element.removeClass(b.RESET).addClass("affix");var a=this.$window.scrollTop(),c=this.$element.offset();return this.pinnedOffset=c.top-a},b.prototype.checkPositionWithEventLoop=function(){setTimeout(a.proxy(this.checkPosition,this),1)},b.prototype.checkPosition=function(){if(this.$element.is(":visible")){var c=a(document).height(),d=this.$window.scrollTop(),e=this.$element.offset(),f=this.options.offset,g=f.top,h=f.bottom;"top"==this.affixed&&(e.top+=d),"object"!=typeof f&&(h=g=f),"function"==typeof g&&(g=f.top(this.$element)),"function"==typeof h&&(h=f.bottom(this.$element));var i=null!=this.unpin&&d+this.unpin<=e.top?!1:null!=h&&e.top+this.$element.height()>=c-h?"bottom":null!=g&&g>=d?"top":!1;if(this.affixed!==i){this.unpin&&this.$element.css("top","");var j="affix"+(i?"-"+i:""),k=a.Event(j+".bs.affix");this.$element.trigger(k),k.isDefaultPrevented()||(this.affixed=i,this.unpin="bottom"==i?this.getPinnedOffset():null,this.$element.removeClass(b.RESET).addClass(j).trigger(a.Event(j.replace("affix","affixed"))),"bottom"==i&&this.$element.offset({top:c-h-this.$element.height()}))}}};var c=a.fn.affix;a.fn.affix=function(c){return this.each(function(){var d=a(this),e=d.data("bs.affix"),f="object"==typeof c&&c;e||d.data("bs.affix",e=new b(this,f)),"string"==typeof c&&e[c]()})},a.fn.affix.Constructor=b,a.fn.affix.noConflict=function(){return a.fn.affix=c,this},a(window).on("load",function(){a('[data-spy="affix"]').each(function(){var b=a(this),c=b.data();c.offset=c.offset||{},c.offsetBottom&&(c.offset.bottom=c.offsetBottom),c.offsetTop&&(c.offset.top=c.offsetTop),b.affix(c)})})}(jQuery); \ No newline at end of file diff --git a/src/main/resources/help_html/js/scripts.js b/src/main/resources/help_html/js/scripts.js new file mode 100644 index 000000000000..a43258652273 --- /dev/null +++ b/src/main/resources/help_html/js/scripts.js @@ -0,0 +1,31 @@ + +$(document).ready(function(){/* activate sidebar */ +$('#sidebar').affix({ + offset: { + top: 235 + } +}); + +/* activate scrollspy menu */ +var $body = $(document.body); +var navHeight = $('.navbar').outerHeight(true) + 10; + +$body.scrollspy({ + target: '#leftCol', + offset: navHeight +}); + +/* smooth scrolling sections */ +$('a[href*=#]:not([href=#])').click(function() { + if (location.pathname.replace(/^\//,'') == this.pathname.replace(/^\//,'') && location.hostname == this.hostname) { + var target = $(this.hash); + target = target.length ? target : $('[name=' + this.hash.slice(1) +']'); + if (target.length) { + $('html,body').animate({ + scrollTop: target.offset().top - 50 + }, 1000); + return false; + } + } +}); +}); \ No newline at end of file diff --git a/src/main/resources/images/address_book_32.png b/src/main/resources/images/address_book_32.png new file mode 100644 index 0000000000000000000000000000000000000000..29810cf1fd938e8568946ad28ee0c678564804b2 GIT binary patch literal 4214 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 literal 0 HcmV?d00001 diff --git a/src/main/resources/images/calendar.png b/src/main/resources/images/calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..8b2bdf4f1c16ad2abafd46df82582aad743d1846 GIT binary patch literal 1215 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�CA>i!LlVRJ>huB}1y?$JIz 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 + + + + + + + + + + + + diff --git a/src/main/resources/view/ActivityHistoryCard.fxml b/src/main/resources/view/ActivityHistoryCard.fxml new file mode 100644 index 000000000000..67205d95fa4c --- /dev/null +++ b/src/main/resources/view/ActivityHistoryCard.fxml @@ -0,0 +1,9 @@ + + + + + + + + diff --git a/src/main/resources/view/DarkTheme.css b/src/main/resources/view/DarkTheme.css new file mode 100644 index 000000000000..5c40dd469c32 --- /dev/null +++ b/src/main/resources/view/DarkTheme.css @@ -0,0 +1,272 @@ +.background { + -fx-background-color: #1d1d1d; +} + +.label { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: #555555; + -fx-opacity: 0.9; +} + +.label-bright { + -fx-font-size: 11pt; + -fx-font-family: "Segoe UI Semibold"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.label-header { + -fx-font-size: 32pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 1; +} + +.text-field { + -fx-font-size: 12pt; + -fx-font-family: "Segoe UI Semibold"; +} + +.tab-pane { + -fx-padding: 0 0 0 1; +} + +.tab-pane .tab-header-area{ + -fx-padding: 0 0 0 0; + -fx-min-height: 0; + -fx-max-height: 0; +} + +.table-view { + -fx-base: #1d1d1d; + -fx-control-inner-background: #1d1d1d; + -fx-background-color: #1d1d1d; + -fx-table-cell-border-color: transparent; + -fx-table-header-border-color: transparent; + -fx-padding: 5; +} + +.table-view .column-header-background { + -fx-background-color: transparent; +} + +.table-view .column-header, .table-view .filler { + -fx-size: 35; + -fx-border-width: 0 0 1 0; + -fx-background-color: transparent; + -fx-border-color: + transparent + transparent + derive(-fx-base, 80%) + transparent; + -fx-border-insets: 0 10 1 0; +} + +.table-view .column-header .label { + -fx-font-size: 20pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-alignment: center-left; + -fx-opacity: 1; +} + +.table-view:focused .table-row-cell:filled:focused:selected { + -fx-background-color: -fx-focus-color; +} + +.split-pane:horizontal .split-pane-divider { + -fx-border-color: transparent #1d1d1d transparent #1d1d1d; + -fx-background-color: transparent, derive(#1d1d1d,20%); +} + +.list-cell { + -fx-label-padding: 0 0 0 0; + -fx-graphic-text-gap : 0; + -fx-padding: 0 0 0 0; +} + +.list-cell .label { + -fx-text-fill: #010504; +} + +.cell_big_label { + -fx-font-size: 16px; + -fx-text-fill: #010504; +} + +.cell_small_label { + -fx-font-size: 11px; + -fx-text-fill: #010504; +} + +.anchor-pane { + -fx-background-color: derive(#1d1d1d,20%); +} + +.anchor-pane-with-border { + -fx-background-color: derive(#1d1d1d,20%); + -fx-border-color: derive(#1d1d1d,10%); + -fx-border-top-width: 1px; +} + +.status-bar { + -fx-background-color: derive(#1d1d1d,10%); + -fx-text-fill: white; +} + +.status-bar .label { + -fx-text-fill: white; +} + +.status-bar-with-border { + -fx-background-color: derive(#1d1d1d,30%); + -fx-border-color: derive(#1d1d1d,25%); + -fx-border-width: 1px; +} + +.status-bar-with-border .label { + -fx-text-fill: white; +} + +.grid-pane { + -fx-background-color: derive(#1d1d1d,30%); + -fx-border-color: derive(#1d1d1d,30%); + -fx-border-width: 1px; +} + +.grid-pane .anchor-pane { + -fx-background-color: derive(#1d1d1d,30%); +} + +.context-menu { + -fx-background-color: derive(#1d1d1d,50%); +} + +.context-menu .label { + -fx-text-fill: white; +} + +.menu-bar { + -fx-background-color: derive(#1d1d1d,20%); +} + +.menu-bar .label { + -fx-font-size: 14pt; + -fx-font-family: "Segoe UI Light"; + -fx-text-fill: white; + -fx-opacity: 0.9; +} + +.menu .left-container { + -fx-background-color: black; +} + +/* + * Metro style Push Button + * Author: Pedro Duque Vieira + * http://pixelduke.wordpress.com/2012/10/23/jmetro-windows-8-controls-on-java/ + */ +.button { + -fx-padding: 5 22 5 22; + -fx-border-color: #e2e2e2; + -fx-border-width: 2; + -fx-background-radius: 0; + -fx-background-color: #1d1d1d; + -fx-font-family: "Segoe UI", Helvetica, Arial, sans-serif; + -fx-font-size: 11pt; + -fx-text-fill: #d8d8d8; + -fx-background-insets: 0 0 0 0, 0, 1, 2; +} + +.button:hover { + -fx-background-color: #3a3a3a; +} + +.button:pressed, .button:default:hover:pressed { + -fx-background-color: white; + -fx-text-fill: #1d1d1d; +} + +.button:focused { + -fx-border-color: white, white; + -fx-border-width: 1, 1; + -fx-border-style: solid, segments(1, 1); + -fx-border-radius: 0, 0; + -fx-border-insets: 1 1 1 1, 0; +} + +.button:disabled, .button:default:disabled { + -fx-opacity: 0.4; + -fx-background-color: #1d1d1d; + -fx-text-fill: white; +} + +.button:default { + -fx-background-color: -fx-focus-color; + -fx-text-fill: #ffffff; +} + +.button:default:hover { + -fx-background-color: derive(-fx-focus-color,30%); +} + +.dialog-pane{ + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.button-bar > *.container{ + -fx-background-color: #1d1d1d; +} + +.dialog-pane > *.label.content{ + -fx-font-size: 14px; + -fx-font-weight: bold; + -fx-text-fill: white; +} + +.dialog-pane:header *.header-panel{ + -fx-background-color: derive(#1d1d1d,25%); +} + +.dialog-pane:header *.header-panel *.label{ + -fx-font-size: 18px; + -fx-font-style: italic; + -fx-fill: white; + -fx-text-fill: white; +} + +#cardPane { + -fx-background-color: transparent; + -fx-border-color: #d6d6d6; + -fx-border-width: 1 1 1 8; +} + +#commandTypeLabel { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#remoteRequestOngoingIndicator { + -fx-font-size: 11px; + -fx-text-fill: #F70D1A; +} + +#commandStateDisplayRootNode { + -fx-background-radius: 5; + -fx-background-color: white; + -fx-padding-background-color: transparent; + -fx-padding: 0 5 0 5; + -fx-effect: dropshadow(three-pass-box, rgba(0,0,0,0.8), 10, 0, 0, 0); +} + +#activityLabel { + -fx-font-size: 12px; +} + +#activityHistoryCardMainpane { + -fx-border-width: 0 0 1 0; + -fx-border-color: lightgrey; + -fx-padding: 2 4 2 4; +} + diff --git a/src/main/resources/view/DefaultBrowserPlaceHolderScreen.fxml b/src/main/resources/view/DefaultBrowserPlaceHolderScreen.fxml new file mode 100644 index 000000000000..bc761118235a --- /dev/null +++ b/src/main/resources/view/DefaultBrowserPlaceHolderScreen.fxml @@ -0,0 +1,21 @@ + + + + + + + + + + + + diff --git a/src/main/resources/view/Extensions.css b/src/main/resources/view/Extensions.css new file mode 100644 index 000000000000..606c927d9a42 --- /dev/null +++ b/src/main/resources/view/Extensions.css @@ -0,0 +1,16 @@ + +.error { + -fx-background-color: red; +} + + +.tag-selector { + -fx-border-width: 1; + -fx-border-color: white; + -fx-border-radius: 3; + -fx-background-radius: 3; +} + +.tooltip-text { + -fx-text-fill: white; +} \ No newline at end of file diff --git a/src/main/resources/view/Help.fxml b/src/main/resources/view/Help.fxml new file mode 100644 index 000000000000..830f160402c1 --- /dev/null +++ b/src/main/resources/view/Help.fxml @@ -0,0 +1,8 @@ + + + + + + + + diff --git a/src/main/resources/view/PersonListCard.fxml b/src/main/resources/view/PersonListCard.fxml new file mode 100644 index 000000000000..bbb99ac735f1 --- /dev/null +++ b/src/main/resources/view/PersonListCard.fxml @@ -0,0 +1,42 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/PersonListPanel.fxml b/src/main/resources/view/PersonListPanel.fxml new file mode 100644 index 000000000000..bd1da9c83de7 --- /dev/null +++ b/src/main/resources/view/PersonListPanel.fxml @@ -0,0 +1,14 @@ + + + + + + + + + + + + + + diff --git a/src/main/resources/view/RootLayout.fxml b/src/main/resources/view/RootLayout.fxml new file mode 100644 index 000000000000..15bd795a64d1 --- /dev/null +++ b/src/main/resources/view/RootLayout.fxml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/resources/view/StatusBarFooter.fxml b/src/main/resources/view/StatusBarFooter.fxml new file mode 100644 index 000000000000..1012ccdd7799 --- /dev/null +++ b/src/main/resources/view/StatusBarFooter.fxml @@ -0,0 +1,13 @@ + + + + + + + + + + + + + diff --git a/src/main/resources/view/TagList.fxml b/src/main/resources/view/TagList.fxml new file mode 100644 index 000000000000..8bfc6ee944d6 --- /dev/null +++ b/src/main/resources/view/TagList.fxml @@ -0,0 +1,7 @@ + + + + + + + diff --git a/src/main/resources/view/TagListCard.fxml b/src/main/resources/view/TagListCard.fxml new file mode 100644 index 000000000000..45c08e08de1a --- /dev/null +++ b/src/main/resources/view/TagListCard.fxml @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + diff --git a/src/test/data/XmlUtilTest/empty.xml b/src/test/data/XmlUtilTest/empty.xml new file mode 100644 index 000000000000..e69de29bb2d1 diff --git a/src/test/data/XmlUtilTest/tempAddressBook.xml b/src/test/data/XmlUtilTest/tempAddressBook.xml new file mode 100644 index 000000000000..41eeb8eb391a --- /dev/null +++ b/src/test/data/XmlUtilTest/tempAddressBook.xml @@ -0,0 +1,15 @@ + + + + 1 + John + Doe + + + + + + + Friends + + diff --git a/src/test/data/XmlUtilTest/validAddressBook.xml b/src/test/data/XmlUtilTest/validAddressBook.xml new file mode 100644 index 000000000000..94242ffcff7c --- /dev/null +++ b/src/test/data/XmlUtilTest/validAddressBook.xml @@ -0,0 +1,24 @@ + + + + 1 + + Cornelia + + Meier + + + + + 2 + + Werner + damithc + Meyer + + + + + Friends + + diff --git a/src/test/java/address/TestApp.java b/src/test/java/address/TestApp.java new file mode 100644 index 000000000000..5383c15ec867 --- /dev/null +++ b/src/test/java/address/TestApp.java @@ -0,0 +1,77 @@ +package address; + +import address.model.UserPrefs; +import address.model.datatypes.ReadOnlyAddressBook; +import address.storage.StorageAddressBook; +import address.testutil.TestUtil; +import address.util.Config; +import address.util.GuiSettings; +import javafx.stage.Screen; +import javafx.stage.Stage; + +import java.io.File; +import java.util.function.Supplier; + +/** + * This class is meant to override some properties of MainApp so that it will be suited for + * testing + */ +public class TestApp extends MainApp { + + public static final String SAVE_LOCATION_FOR_TESTING = TestUtil.appendToSandboxPath("sampleData.xml"); + protected static final String DEFAULT_PREF_FILE_LOCATION_FOR_TESTING = TestUtil.appendToSandboxPath("pref_testing.json"); + public static final String APP_TITLE = "Test App"; + protected static final String ADDRESS_BOOK_NAME = "Test"; + protected Supplier initialDataSupplier = () -> null; + protected String saveFileLocation = SAVE_LOCATION_FOR_TESTING; + + public TestApp() { + } + + public TestApp(Supplier initialDataSupplier, String saveFileLocation) { + super(); + this.initialDataSupplier = initialDataSupplier; + this.saveFileLocation = saveFileLocation; + + // If some initial local data has been provided, write those to the file + if (initialDataSupplier.get() != null) { + TestUtil.createDataFileWithData( + new StorageAddressBook(this.initialDataSupplier.get()), + this.saveFileLocation); + } + } + + @Override + protected Config initConfig(String configFilePath) { + Config config = super.initConfig(configFilePath); + config.setAppTitle(APP_TITLE); + config.setLocalDataFilePath(saveFileLocation); + config.setPrefsFileLocation(new File(DEFAULT_PREF_FILE_LOCATION_FOR_TESTING)); + config.setAddressBookName(ADDRESS_BOOK_NAME); + return config; + } + + @Override + protected UserPrefs initPrefs(Config config) { + UserPrefs userPrefs = super.initPrefs(config); + double x = Screen.getPrimary().getVisualBounds().getMinX(); + double y = Screen.getPrimary().getVisualBounds().getMinY(); + userPrefs.setGuiSettings(new GuiSettings(600.0, 600.0, (int) x, (int) y)); + return userPrefs; + } + + + @Override + public void start(Stage primaryStage) { + ui.start(primaryStage); + storageManager.start(); + } + + public Config getTestingConfig() { + return this.config; + } + + public static void main(String[] args) { + launch(args); + } +} diff --git a/src/test/java/address/parser/ParserTest.java b/src/test/java/address/parser/ParserTest.java new file mode 100644 index 000000000000..1002d6a3303b --- /dev/null +++ b/src/test/java/address/parser/ParserTest.java @@ -0,0 +1,131 @@ +package address.parser; + +import address.model.datatypes.person.ReadOnlyPerson; +import address.model.datatypes.tag.Tag; +import address.parser.expr.Expr; +import address.testutil.PersonBuilder; +import org.junit.Before; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +public class ParserTest { + Parser parser; + + @Before + public void setup() { + parser = new Parser(); + } + + @Test + public void parser_multipleQualifiers_correctExprProduced() throws ParseException { + String filterString = "name:Mueller tag:friends"; + Expr expr = parser.parse(filterString); + + ReadOnlyPerson readOnlyViewablePerson = buildPerson(1, "John", "Mueller", "", "", "friends"); + + assertTrue(expr.satisfies(readOnlyViewablePerson)); + } + + @Test + public void parser_containsNotExprAndMultipleQualifiers_correctExprProduced() throws ParseException { + String filterString = "!name:Mueller tag:friends"; + Expr expr = parser.parse(filterString); + + ReadOnlyPerson personOne = buildPerson(1, "John", "Mueller", "", "", "friends"); + ReadOnlyPerson personTwo = buildPerson(2, "John", "Tan", "", "", "friends"); + ReadOnlyPerson personThree = buildPerson(3, "John", "Lee", "", "", "colleagues"); + + assertFalse(expr.satisfies(personOne)); + assertTrue(expr.satisfies(personTwo)); + assertFalse(expr.satisfies(personThree)); + } + + @Test + public void parser_multipleNotExprAndMultipleQualifiers_correctExprProduced() throws ParseException { + String filterString = "!name:Mueller !tag:friends !!city:Singapore"; + Expr expr = parser.parse(filterString); + + ReadOnlyPerson personOne = buildPerson(1, "John", "Mueller", "", "", "friends"); + ReadOnlyPerson personTwo = buildPerson(2, "John", "Tan", "", "Singapore", "friends"); + ReadOnlyPerson personThree = buildPerson(3, "Mull", "Lee", "", "Malaysia", "colleagues"); + ReadOnlyPerson personFour = buildPerson(4, "Jack", "Lim", "", "Singapore", "colleagues"); + + assertFalse(expr.satisfies(personOne)); + assertFalse(expr.satisfies(personTwo)); + assertFalse(expr.satisfies(personThree)); + assertTrue(expr.satisfies(personFour)); + } + + @Test + public void parser_allQualifiers_correctExprProduced() throws ParseException { + String filterString = "name:Mueller tag:friends city:Singapore street:Victoria id:5"; + Expr expr = parser.parse(filterString); + + ReadOnlyPerson personOne = buildPerson(1, "John", "Tan", "", "Singapore", "friends"); + ReadOnlyPerson personTwo = buildPerson(2, "John", "Mueller", "Victoria Street", "Singapore", + "friends"); + ReadOnlyPerson personThree = buildPerson(3, "Mull", "Lee", "Johor Street", "Malaysia", + "colleagues"); + ReadOnlyPerson personFour = buildPerson(4, "Jack", "Lim", "Heng Mui Keng Terrace", "Singapore", + "colleagues"); + ReadOnlyPerson personFive = buildPerson(5, "Martin", "Mueller", "Victoria Street", "Singapore", + "friends"); + + assertFalse(expr.satisfies(personOne)); + assertFalse(expr.satisfies(personTwo)); + assertFalse(expr.satisfies(personThree)); + assertFalse(expr.satisfies(personFour)); + assertTrue(expr.satisfies(personFive)); + } + + @Test + public void parser_invalidFilterString_parseExceptionThrown() { + // tag should not have s + String filterString = "firstName:John lastName:Mueller tags:friends city:Singapore street:Victoria id:5"; + // missing qualifier value for city + String filterStringTwo = "firstName:John lastName:Mueller tags:friends city: street:Victoria id:5"; + // unknown symbol + String filterStringThree = "firstName:John lastName:Mueller & tags:friends city: street:Victoria id:5"; + // space after city colon + String filterStringFour = "firstName:John lastName:Mueller tags:friends city: Singapore street:Victoria id:5"; + + assertTrue(isParseExceptionThrown(filterString)); + assertTrue(isParseExceptionThrown(filterStringTwo)); + assertTrue(isParseExceptionThrown(filterStringThree)); + assertTrue(isParseExceptionThrown(filterStringFour)); + } + + @Test + public void parser_invalidValue_parseExceptionThrown() { + // not an integer + String filterString = "id:one"; + assertTrue(isParseExceptionThrown(filterString)); + } + + private boolean isParseExceptionThrown(String filterString) { + try { + parser.parse(filterString); + return false; + } catch (ParseException e) { + return true; + } + } + + private ReadOnlyPerson buildPerson(int id, String firstName, String lastName, String street, + String city, String... tags) { + + List tagList = new ArrayList<>(); + for (String tagString : tags) { + tagList.add(new Tag(tagString)); + } + return new PersonBuilder(firstName, lastName, id).withStreet(street) + .withCity(city) + .withTags(tagList.toArray(new Tag[tagList.size()])) + .build(); + } +} diff --git a/src/test/java/address/storage/StorageManagerTest.java b/src/test/java/address/storage/StorageManagerTest.java new file mode 100644 index 000000000000..988ea680b8e0 --- /dev/null +++ b/src/test/java/address/storage/StorageManagerTest.java @@ -0,0 +1,153 @@ +package address.storage; + + +import address.events.model.LocalModelChangedEvent; +import address.events.storage.SaveDataRequestEvent; +import address.exceptions.DataConversionException; +import address.exceptions.DuplicateTagException; +import address.model.ModelManager; +import address.model.UserPrefs; +import address.model.datatypes.AddressBook; +import address.model.datatypes.ReadOnlyAddressBook; +import address.testutil.AddressBookBuilder; +import address.testutil.SerializableTestClass; +import address.testutil.TestUtil; + +import address.util.Config; +import commons.FileUtil; + +import org.junit.After; +import org.junit.Before; +import org.junit.Test; + +import java.io.File; +import java.io.FileNotFoundException; +import java.io.IOException; + +import static junit.framework.TestCase.assertNotNull; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertNotEquals; + +public class StorageManagerTest { + private static final File SERIALIZATION_FILE = new File(TestUtil.appendToSandboxPath("serialize.json")); + private static final String DEFAULT_CONFIG_FILE = TestUtil.appendToSandboxPath("config.json"); + private static final String DEFAULT_PREF_FILE = TestUtil.appendToSandboxPath("preferences.json"); + private static final String TESTING_DATA_FILE_PATH = TestUtil.appendToSandboxPath("dummyAddressBook.xml"); + private static final File TESTING_DATA_FILE = new File(TESTING_DATA_FILE_PATH); + private static final String TEMP_SAVE_FILE_PATH = TestUtil.appendToSandboxPath("tempAddressBook.xml"); + private static final File TEMP_SAVE_FILE = new File(TEMP_SAVE_FILE_PATH); + private StorageManager storageManager; + private ModelManager modelManager; + private Config config; + private UserPrefs prefs; + + @Before + public void before() throws IOException, DataConversionException { + StorageManager.saveAddressBook(TESTING_DATA_FILE, new AddressBook()); + config = StorageManager.getConfig(DEFAULT_CONFIG_FILE); + config.setLocalDataFilePath(TESTING_DATA_FILE_PATH); + prefs = StorageManager.getUserPrefs(new File(DEFAULT_PREF_FILE)); + modelManager = new ModelManager(config); + storageManager = new StorageManager(modelManager::resetData, modelManager::getDefaultAddressBook, + config, prefs); + } + + @After + public void after() { + TESTING_DATA_FILE.delete(); + } + + @Test + public void recreateFile() {} // This is not implemented as it requires reflection + @Test + public void createAndWriteToConfigFile() {} // This is not implemented as it requires reflection + @Test + public void deleteConfigFileIfExists() {} // This is not implemented as it requires reflection + @Test + public void readFromConfigFile() {} // This is not implemented as it requires reflection + + /** + * This is not implemented due to the need to mock static methods of StorageManager which will prevent some + * real methods to be called, hence leaving other unrelated methods untested + */ + @Test + public void savePrefsToFile_correspondingMethodCalled() {} + + @Test + public void loadDataFromFile() {} // This is not implemented as it requires reflection + + @Test + public void testHandleLoadDataRequestEvent() { + //Not tested, can't figure out a proper way to load new data and check if the model is updated. + } + + @Test + public void testGetConfig() { + //This is called in the before() method. Already tested indirectly. + } + + @Test + public void serializeObjectToJsonFile_noExceptionThrown() throws IOException { + SerializableTestClass serializableTestClass = new SerializableTestClass(); + serializableTestClass.setTestValues(); + + FileUtil.serializeObjectToJsonFile(SERIALIZATION_FILE, serializableTestClass); + + assertEquals(FileUtil.readFromFile(SERIALIZATION_FILE), SerializableTestClass.JSON_STRING_REPRESENTATION); + } + + @Test + public void deserializeObjectFromJsonFile_noExceptionThrown() throws IOException { + FileUtil.writeToFile(SERIALIZATION_FILE, SerializableTestClass.JSON_STRING_REPRESENTATION); + + SerializableTestClass serializableTestClass = FileUtil + .deserializeObjectFromJsonFile(SERIALIZATION_FILE, SerializableTestClass.class); + + assertEquals(serializableTestClass.getName(), SerializableTestClass.getNameTestValue()); + assertEquals(serializableTestClass.getListOfLocalDateTimes(), SerializableTestClass.getListTestValues()); + assertEquals(serializableTestClass.getMapOfIntegerToString(), SerializableTestClass.getHashMapTestValues()); + } + + @Test + public void saveDataToFile() throws FileNotFoundException, DataConversionException { + ReadOnlyAddressBook oldAb = new AddressBook(storageManager.getData()); + AddressBook editedAb = new AddressBookBuilder(new AddressBook(storageManager.getData())).withPerson("second", "person").build(); + assertNotEquals(editedAb, oldAb); + + storageManager.saveDataToFile(TEMP_SAVE_FILE, editedAb); + + ReadOnlyAddressBook savedAddressbook = new AddressBook(XmlFileStorage.loadDataFromSaveFile(TEMP_SAVE_FILE)); + + assertEquals(editedAb.getPersonList(), savedAddressbook.getPersonList()); + assertEquals(editedAb.getTagList(), savedAddressbook.getTagList()); + } + + @Test + public void testConfig_openConfigFile_exist() { + Config config = StorageManager.getConfig(DEFAULT_CONFIG_FILE); + assertNotNull(config); + } + + @Test + public void testHandleLocalModelChangedEvent() throws DuplicateTagException, InterruptedException, FileNotFoundException, DataConversionException { + ReadOnlyAddressBook oldAb = new AddressBook(storageManager.getData()); + AddressBook editedAb = new AddressBookBuilder(new AddressBook(storageManager.getData())).withPerson("second", "person").build(); + assertNotEquals(editedAb, oldAb); + + storageManager.handleLocalModelChangedEvent(new LocalModelChangedEvent(editedAb)); + AddressBook newAb = new AddressBook(storageManager.getData()); + + assertEquals(editedAb.getPersonList(), newAb.getPersonList()); + assertEquals(editedAb.getTagList(), newAb.getTagList()); + } + + @Test + public void testHandleSaveDataRequestEvent() throws FileNotFoundException, DataConversionException { + storageManager.handleSaveDataRequestEvent(new SaveDataRequestEvent(TEMP_SAVE_FILE, storageManager.getData())); + StorageAddressBook storageAddressBook = XmlFileStorage.loadDataFromSaveFile(TEMP_SAVE_FILE); + assertEquals(storageAddressBook.getPersonList(), storageManager.getData().getPersonList()); + assertEquals(storageAddressBook.getTagList(), storageManager.getData().getTagList()); + } + + //TODO: finish the rest of the public methods in StorageManager +} diff --git a/src/test/java/address/testutil/AddressBookBuilder.java b/src/test/java/address/testutil/AddressBookBuilder.java new file mode 100644 index 000000000000..243efca90dff --- /dev/null +++ b/src/test/java/address/testutil/AddressBookBuilder.java @@ -0,0 +1,38 @@ +package address.testutil; + +import address.model.datatypes.AddressBook; +import address.model.datatypes.person.Person; +import address.model.datatypes.tag.Tag; + +/** + * A utility class to help with building Addressbook objects. + * Example usage:
+ * {@code AddressBook ab = new AddressBookBuilder().withPerson("John", "Doe").withTag("Friend").build();} + */ +public class AddressBookBuilder { + + private AddressBook addressBook; + private int idCounter = 1; + + public AddressBookBuilder(){ + addressBook = new AddressBook(); + } + + public AddressBookBuilder(AddressBook addressBook){ + this.addressBook = addressBook; + } + + public AddressBookBuilder withPerson(String firstName, String lastName){ + addressBook.addPerson(new Person(firstName, lastName, idCounter++)); + return this; + } + + public AddressBookBuilder withTag(String tagName){ + addressBook.addTag(new Tag(tagName)); + return this; + } + + public AddressBook build(){ + return addressBook; + } +} diff --git a/src/test/java/address/testutil/BaseEventSubscriber.java b/src/test/java/address/testutil/BaseEventSubscriber.java new file mode 100644 index 000000000000..3914d27a2d7c --- /dev/null +++ b/src/test/java/address/testutil/BaseEventSubscriber.java @@ -0,0 +1,12 @@ +package address.testutil; + +import address.events.BaseEvent; + +/** + * + */ +public interface BaseEventSubscriber { + + public void receive(BaseEvent e); + +} diff --git a/src/test/java/address/testutil/PersonBuilder.java b/src/test/java/address/testutil/PersonBuilder.java new file mode 100644 index 000000000000..6da90d4968b7 --- /dev/null +++ b/src/test/java/address/testutil/PersonBuilder.java @@ -0,0 +1,82 @@ +package address.testutil; + +import address.model.datatypes.person.Person; +import address.model.datatypes.tag.Tag; +import commons.DateTimeUtil; + +import java.util.Arrays; + +/** + * A utility class to build Person objects using a fluent interface. + */ +public class PersonBuilder { + public static final int STUB_ID = -99; + private final Person person; + + /** + * Creates a person builder with the given firstName and lastName. + */ + public PersonBuilder(String firstName, String lastName) { + this.person = new Person(firstName, lastName, STUB_ID); + } + + /** + * Creates a person builder with the given firstName, lastName and ID. + */ + public PersonBuilder(String firstName, String lastName, int id) { + this.person = new Person(firstName, lastName, id); + } + + /** + * Creates a person builder with the given Person object. + * The person object will be mutated during the building process. + * @param initial + */ + public PersonBuilder(Person initial) { + this.person = initial; + } + + public PersonBuilder withFirstName(String firstName) { + person.setFirstName(firstName); + return this; + } + + public PersonBuilder withLastName(String lastName) { + person.setLastName(lastName); + return this; + } + + public PersonBuilder withStreet(String street) { + person.setStreet(street); + return this; + } + + public PersonBuilder withCity(String city) { + person.setCity(city); + return this; + } + + public PersonBuilder withPostalCode(String postalCode){ + person.setPostalCode(postalCode); + return this; + } + + public PersonBuilder withBirthday(String birthday){ + person.setBirthday(DateTimeUtil.parse(birthday)); + return this; + } + + public PersonBuilder withGithubUsername(String githubUsername){ + person.setGithubUsername(githubUsername); + return this; + } + + public PersonBuilder withTags(Tag... tags){ + person.setTags(Arrays.asList(tags)); + return this; + } + + public Person build() { + return person; + } +} diff --git a/src/test/java/address/testutil/ScreenShotRule.java b/src/test/java/address/testutil/ScreenShotRule.java new file mode 100644 index 000000000000..4f2ec5fe51a9 --- /dev/null +++ b/src/test/java/address/testutil/ScreenShotRule.java @@ -0,0 +1,17 @@ +package address.testutil; + +import org.junit.rules.TestWatcher; +import org.junit.runner.Description; + +import java.io.File; + +/** + * Capture screenshot after each failed test + */ +public class ScreenShotRule extends TestWatcher{ + + @Override + protected void succeeded(Description description) { + new File(description.getClassName() + description.getMethodName() + ".png").delete(); + } +} diff --git a/src/test/java/address/testutil/SerializableTestClass.java b/src/test/java/address/testutil/SerializableTestClass.java new file mode 100644 index 000000000000..47eeb6df5f5c --- /dev/null +++ b/src/test/java/address/testutil/SerializableTestClass.java @@ -0,0 +1,73 @@ +package address.testutil; + +import java.time.LocalDateTime; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.List; + +/** + * A class used to test serialization and deserialization + */ +public class SerializableTestClass { + public static final String JSON_STRING_REPRESENTATION = String.format("{%n" + + " \"name\" : \"This is a test class\",%n" + + " \"listOfLocalDateTimes\" : " + + "[ \"-999999999-01-01T00:00:00\", \"+999999999-12-31T23:59:59.999999999\", \"0001-01-01T01:01:00\" ],%n" + + " \"mapOfIntegerToString\" : {%n" + + " \"1\" : \"One\",%n" + + " \"2\" : \"Two\",%n" + + " \"3\" : \"Three\"%n" + + " }%n" + + "}"); + + private static final String NAME_TEST_VALUE = "This is a test class"; + + private String name; + + private List listOfLocalDateTimes; + private HashMap mapOfIntegerToString; + + public SerializableTestClass() {} + + public static String getNameTestValue() { + return NAME_TEST_VALUE; + } + + public static List getListTestValues() { + List listOfLocalDateTimes = new ArrayList<>(); + + listOfLocalDateTimes.add(LocalDateTime.MIN); + listOfLocalDateTimes.add(LocalDateTime.MAX); + listOfLocalDateTimes.add(LocalDateTime.of(1, 1, 1, 1, 1)); + + return listOfLocalDateTimes; + } + + public static HashMap getHashMapTestValues() { + HashMap mapOfIntegerToString = new HashMap<>(); + + mapOfIntegerToString.put(1, "One"); + mapOfIntegerToString.put(2, "Two"); + mapOfIntegerToString.put(3, "Three"); + + return mapOfIntegerToString; + } + + public void setTestValues() { + name = getNameTestValue(); + listOfLocalDateTimes = getListTestValues(); + mapOfIntegerToString = getHashMapTestValues(); + } + + public String getName() { + return name; + } + + public List getListOfLocalDateTimes() { + return listOfLocalDateTimes; + } + + public HashMap getMapOfIntegerToString() { + return mapOfIntegerToString; + } +} diff --git a/src/test/java/address/testutil/TestDataGenerator.java b/src/test/java/address/testutil/TestDataGenerator.java new file mode 100644 index 000000000000..3470eb5f9f4a --- /dev/null +++ b/src/test/java/address/testutil/TestDataGenerator.java @@ -0,0 +1,68 @@ +package address.testutil; + +import address.exceptions.DataConversionException; +import address.model.datatypes.AddressBook; +import address.model.datatypes.person.Person; +import address.model.datatypes.tag.Tag; +import address.storage.StorageManager; + +import java.io.File; +import java.io.IOException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.List; +import java.util.Random; +import java.util.stream.Collectors; +import java.util.stream.IntStream; + +/** + * A utility class to generate data for testing. + */ +public class TestDataGenerator { + + private static String DATA_FILE_NAME = "scalabilitydata.xml"; + private static int NO_OF_PERSONS = 1000; + private static List allTags = generateTagList(); + + private static List generateTagList() { + List tags = new ArrayList<>(); + tags.add(new Tag("friends")); + tags.add(new Tag("colleagues")); + tags.add(new Tag("relatives")); + return tags; + } + + public static void main(String[] args) throws IOException, DataConversionException { + StorageManager.saveAddressBook(new File(DATA_FILE_NAME), generateData()); + } + + private static AddressBook generateData() { + AddressBook ab = new AddressBook(); + ab.setTags(allTags); + + IntStream.range(0, NO_OF_PERSONS) + .forEach(i -> ab.addPerson(generatePerson(i))); + + return ab; + } + + private static Person generatePerson(int i) { + Person p = new Person("FirstName" + i, "LastName" + i, i); + p.setCity("City " + i); + p.setGithubUsername("dummy"); + p.setPostalCode("123456"); + p.setBirthday(LocalDate.now().minusDays(i)); + p.setTags(getRandomListOfTags()); + return p; + } + + /** + * Returns a random subset of the sample tags. + */ + private static List getRandomListOfTags() { + return allTags.stream() + .filter(t -> new Random().nextBoolean()) + .collect(Collectors.toList()); + } + +} diff --git a/src/test/java/address/testutil/TestUtil.java b/src/test/java/address/testutil/TestUtil.java new file mode 100644 index 000000000000..97f6bba9613f --- /dev/null +++ b/src/test/java/address/testutil/TestUtil.java @@ -0,0 +1,350 @@ +package address.testutil; + +import address.TestApp; +import address.model.datatypes.AddressBook; +import address.model.datatypes.person.Person; +import address.model.datatypes.tag.Tag; +import address.storage.StorageAddressBook; +import com.google.common.io.Files; +import commons.FileUtil; +import commons.OsDetector; +import commons.XmlUtil; +import guitests.guihandles.PersonCardHandle; +import javafx.geometry.Bounds; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.Scene; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.KeyCombination; +import javafx.scene.input.KeyEvent; +import junit.framework.AssertionFailedError; +import org.loadui.testfx.GuiTest; +import org.testfx.api.FxToolkit; + +import java.io.File; +import java.io.IOException; +import java.lang.reflect.Field; +import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeoutException; +import java.util.stream.Collectors; + +/** + * A utility class for test cases. + */ +public class TestUtil { + + public static void assertThrows(Class expected, Runnable executable) { + try { + executable.run(); + } + catch (Throwable actualException) { + if (!actualException.getClass().isAssignableFrom(expected)) { + String message = String.format("Expected thrown: %s, actual: %s", expected.getName(), + actualException.getClass().getName()); + throw new AssertionFailedError(message); + } else return; + } + throw new AssertionFailedError( + String.format("Expected %s to be thrown, but nothing was thrown.", expected.getName())); + } + + /** + * Folder used for temp files created during testing. Ignored by Git. + */ + public static String SANDBOX_FOLDER = FileUtil.getPath("./src/test/data/sandbox/"); + + public static final Person[] samplePersonData = { + new Person("Hans", "Muster", 1), + new Person("Ruth", "Mueller", 2), + new Person("Heinz", "Kurz", 3), + new Person("Cornelia", "Meier", 4), + new Person("Werner", "Meyer", 5), + new Person("Lydia", "Kunz", 6), + new Person("Anna", "Best", 7), + new Person("Stefan", "Meier", 8), + new Person("Martin", "Mueller", 9) + }; + + public static final Tag[] sampleTagData = { + new Tag("relatives"), + new Tag("friends") + }; + + public static Person generateSamplePersonWithAllData(int customId) { + final Person p = new Person("first", "last", customId); + p.setStreet("some street"); + p.setPostalCode("1234"); + p.setCity("some city"); + p.setGithubUsername("SomeName"); + p.setBirthday(LocalDate.now()); + p.setTags(Arrays.asList(new Tag("A"), new Tag("B"))); + return p; + } + + public static List generateSamplePersonData() { + return Arrays.asList(samplePersonData); + } + + /** + * Appends the file name to the sandbox folder path + * @param fileName + * @return + */ + public static String appendToSandboxPath(String fileName) { + return SANDBOX_FOLDER + fileName; + } + + public static void createDataFileWithSampleData(String filePath) { + createDataFileWithData(generateSampleStorageAddressBook(), filePath); + } + + public static void createDataFileWithData(T data, String filePath) { + try { + File saveFileForTesting = new File(filePath); + FileUtil.createIfMissing(saveFileForTesting); + XmlUtil.saveDataToFile(saveFileForTesting, data); + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + public static void main(String... s) { + createDataFileWithSampleData(TestApp.SAVE_LOCATION_FOR_TESTING); + } + + public static AddressBook generateSampleAddressBook() { + return new AddressBook(Arrays.asList(samplePersonData), Arrays.asList(sampleTagData)); + } + + public static StorageAddressBook generateSampleStorageAddressBook() { + return new StorageAddressBook(generateSampleAddressBook()); + } + + /** + * Tweaks the {@code keyCodeCombination} to resolve the {@code KeyCode.SHORTCUT} to their + * respective platform-specific keycodes + */ + public static KeyCode[] scrub(KeyCodeCombination keyCodeCombination) { + List keys = new ArrayList<>(); + if (keyCodeCombination.getAlt() == KeyCombination.ModifierValue.DOWN) { + keys.add(KeyCode.ALT); + } + if (keyCodeCombination.getShift() == KeyCombination.ModifierValue.DOWN) { + keys.add(KeyCode.SHIFT); + } + if (keyCodeCombination.getMeta() == KeyCombination.ModifierValue.DOWN) { + keys.add(KeyCode.META); + } + if (keyCodeCombination.getControl() == KeyCombination.ModifierValue.DOWN) { + keys.add(KeyCode.CONTROL); + } + if (keyCodeCombination.getShortcut() == KeyCombination.ModifierValue.DOWN) { + keys.add(getPlatformSpecificShortcutKey()); + + } + keys.add(keyCodeCombination.getCode()); + return keys.toArray(new KeyCode[]{}); + } + + public static boolean isHeadlessEnvironment() { + String headlessProperty = System.getProperty("testfx.headless"); + return headlessProperty != null && headlessProperty.equals("true"); + } + + /** + * Returns {@code KeyCode.COMMAND or KeyCode.META} if on a Mac depending on headful/headless mode, + * {@code KeyCode.CONTROL} otherwise. + */ + private static KeyCode getPlatformSpecificShortcutKey() { + KeyCode macShortcut = isHeadlessEnvironment() ? KeyCode.COMMAND : KeyCode.META; + return OsDetector.isOnMac() ? macShortcut : KeyCode.CONTROL; + } + + /** + * Replaces any {@code KeyCode.SHORTCUT or KeyCode.META} with {@code KeyCode.META or KeyCode.COMMAND} on Macs + * depending on headful/headless mode, and {@code KeyCode.CONTROL} on other platforms. + */ + public static KeyCode[] scrub(KeyCode[] keyCodes) { + for (int i = 0; i < keyCodes.length; i++) { + if (keyCodes[i] == KeyCode.META || keyCodes[i] == KeyCode.SHORTCUT) { + keyCodes[i] = getPlatformSpecificShortcutKey(); + } + } + return keyCodes; + } + + public static void captureScreenShot(String fileName) { + File file = GuiTest.captureScreenshot(); + try { + Files.copy(file, new File(fileName + ".png")); + } catch (IOException e) { + e.printStackTrace(); + } + } + + public static String descOnFail(Object... comparedObjects) { + return "Comparison failed \n" + + Arrays.asList(comparedObjects).stream() + .map(Object::toString) + .collect(Collectors.joining("\n")); + } + + /** + * Generates a minimal {@link KeyEvent} object that matches the {@code keyCombination} + */ + public static KeyEvent getKeyEvent(String keyCombination) { + String[] keys = keyCombination.split(" "); + + String key = keys[keys.length - 1]; + boolean shiftDown = keyCombination.toLowerCase().contains("shift"); + boolean metaDown = keyCombination.toLowerCase().contains("meta") + || (keyCombination.toLowerCase().contains("shortcut") && OsDetector.isOnMac()); + boolean altDown = keyCombination.toLowerCase().contains("alt"); + boolean ctrlDown = keyCombination.toLowerCase().contains("ctrl") + || keyCombination.toLowerCase().contains("shortcut") && !OsDetector.isOnMac(); + return new KeyEvent(null, null, null, KeyCode.valueOf(key), shiftDown, ctrlDown, altDown, metaDown); + } + + public static void setFinalStatic(Field field, Object newValue) throws NoSuchFieldException, IllegalAccessException{ + field.setAccessible(true); + // remove final modifier from field + Field modifiersField = Field.class.getDeclaredField("modifiers"); + modifiersField.setAccessible(true); + // ~Modifier.FINAL is used to remove the final modifier from field so that its value is no longer + // final and can be changed + modifiersField.setInt(field, field.getModifiers() & ~Modifier.FINAL); + field.set(null, newValue); + } + + public static void initRuntime() throws TimeoutException { + FxToolkit.registerPrimaryStage(); + FxToolkit.hideStage(); + } + + public static void tearDownRuntime() throws Exception { + FxToolkit.cleanupStages(); + } + + /** + * Gets private method of a class + * Invoke the method using method.invoke(objectInstance, params...) + * + * Caveat: only find method declared in the current Class, not inherited from supertypes + */ + public static Method getPrivateMethod(Class objectClass, String methodName) throws NoSuchMethodException { + Method method = objectClass.getDeclaredMethod(methodName); + method.setAccessible(true); + return method; + } + + public static void renameFile(File file, String newFileName) { + try { + Files.copy(file, new File(newFileName)); + } catch (IOException e1) { + e1.printStackTrace(); + } + } + + /** + * Gets mid point of a node relative to the screen. + * @param node + * @return + */ + public static Point2D getScreenMidPoint(Node node) { + double x = getScreenPos(node).getMinX() + node.getLayoutBounds().getWidth() / 2; + double y = getScreenPos(node).getMinY() + node.getLayoutBounds().getHeight() / 2; + return new Point2D(x,y); + } + + /** + * Gets mid point of a node relative to its scene. + * @param node + * @return + */ + public static Point2D getSceneMidPoint(Node node) { + double x = getScenePos(node).getMinX() + node.getLayoutBounds().getWidth() / 2; + double y = getScenePos(node).getMinY() + node.getLayoutBounds().getHeight() / 2; + return new Point2D(x,y); + } + + /** + * Gets the bound of the node relative to the parent scene. + * @param node + * @return + */ + public static Bounds getScenePos(Node node) { + return node.localToScene(node.getBoundsInLocal()); + } + + public static Bounds getScreenPos(Node node) { + return node.localToScreen(node.getBoundsInLocal()); + } + + public static double getSceneMaxX(Scene scene) { + return scene.getX() + scene.getWidth(); + } + + public static double getSceneMaxY(Scene scene) { + return scene.getX() + scene.getHeight(); + } + + public static String getOsDependentKeyCombinationString(String keyCombinationString) { + if (!OsDetector.isOnMac()) return keyCombinationString; + return keyCombinationString.replaceAll("Alt\\+", "⌥") + .replaceAll("Meta\\+", "⌘") + .replaceAll("Shift\\+", "⇧"); + } + + public static Object getLastElement(List list) { + return list.get(list.size() - 1); + } + + public static Person[] removePersonsFromList(Person[] persons, Person... personsToRemove) { + List listOfPersons = asList(persons); + listOfPersons.removeAll(asList(personsToRemove)); + return listOfPersons.toArray(new Person[listOfPersons.size()]); + } + + public static Person[] replacePersonFromList(Person[] persons, Person person, int index) { + persons[index] = person; + return persons; + } + + public static Person[] addPersonsToList(Person[] persons, Person... personsToAdd) { + List listOfPersons = asList(persons); + listOfPersons.addAll(asList(personsToAdd)); + return listOfPersons.toArray(new Person[listOfPersons.size()]); + } + + private static List asList(T[] objs) { + List list = new ArrayList<>(); + for(T obj : objs) { + list.add(obj); + } + return list; + } + + public static boolean compareCardAndPerson(PersonCardHandle card, Person person) { + return card.getFirstName().equals(person.getFirstName()) && card.getLastName().equals(person.getLastName()); + //TODO: compare birthday, tag list and address. + } + + public static Tag[] getTagList(String tags) { + + if (tags.equals("")) { + return new Tag[]{}; + } + + final String[] split = tags.split(", "); + + final List collect = Arrays.asList(split).stream().map(e -> new Tag(e.replaceFirst("Tag: ", ""))).collect(Collectors.toList()); + + return collect.toArray(new Tag[split.length]); + } +} diff --git a/src/test/java/address/testutil/TypicalTestData.java b/src/test/java/address/testutil/TypicalTestData.java new file mode 100644 index 000000000000..2f29a5d6cb9c --- /dev/null +++ b/src/test/java/address/testutil/TypicalTestData.java @@ -0,0 +1,60 @@ +package address.testutil; + +import address.model.datatypes.AddressBook; +import address.model.datatypes.person.Person; +import address.model.datatypes.tag.Tag; + +/** + * Typical data set used for testing + */ +public class TypicalTestData { + + public Tag colleagues = new Tag("colleagues"); + public Tag friends = new Tag("friends"); + public Tag family = new Tag("family"); + /* + * TODO: add more details to these persons. + * Note that staring letter of names have some pattern. + * First person's name start with A and her second name starts with B. + * The Second person's name starts with B and his other names start with C and D. + * And so on. + */ + + public Person alice = new PersonBuilder(new Person("Alice", "Brown", 1)) + .withStreet("81th Wall Street").withCity("New York") + .withPostalCode("41452").withBirthday("11.09.1983") + .withGithubUsername("alicia").withTags(friends).build(); + public Person benson = new PersonBuilder(new Person("Benson", "Christopher Dean", 2)) + .withStreet("Pittsburgh Street").withCity("Pittsburgh") + .withPostalCode("424456").withGithubUsername("ben333").build(); + public Person charlie = new PersonBuilder(new Person("Charlie", "Davidson", 3)).withCity("Texas") + .withGithubUsername("charlotte").build(); + public Person dan = new PersonBuilder(new Person("Dan", "Edwards", 4)).withBirthday("03.01.1995").build(); + public Person elizabeth = new Person("Elizabeth", "F. Green", 5); + public Person fiona = new PersonBuilder("Fiona", "Wong", 6).withStreet("51th street").withCity("New York") + .withPostalCode("51245").withBirthday("01.01.1980") + .withGithubUsername("fiona").withTags(friends).build(); + public Person george = new PersonBuilder("George", "David", 7).withStreet("8th Ave").withCity("Chicago") + .withPostalCode("25614").withBirthday("01.12.1988") + .withGithubUsername("george").withTags(friends, colleagues) + .build(); + + public AddressBook book; + + public TypicalTestData() { + book = new AddressBook(); + book.addPerson(alice); + book.addPerson(benson); + book.addPerson(charlie); + book.addPerson(dan); + book.addPerson(elizabeth); + book.addTag(colleagues); + book.addTag(friends); + book.addTag(family); + } + + public Person[] getTestData() { + return new Person[] {alice, benson, charlie, dan, elizabeth}; + } + +} diff --git a/src/test/java/address/util/FilteredListTest.java b/src/test/java/address/util/FilteredListTest.java new file mode 100644 index 000000000000..42688803d1de --- /dev/null +++ b/src/test/java/address/util/FilteredListTest.java @@ -0,0 +1,122 @@ +package address.util; + +import address.model.datatypes.person.Person; +import address.testutil.TestUtil; +import address.util.collections.FilteredList; +import commons.StringUtil; +import javafx.collections.FXCollections; +import javafx.collections.ListChangeListener; +import javafx.collections.ObservableList; +import javafx.collections.transformation.TransformationList; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; + +import static org.junit.Assert.*; + +public class FilteredListTest { + // datatype to observe sent changes by filtered list + private class Observer extends TransformationList { + private List> changeList = new ArrayList<>(); + + protected Observer(ObservableList source) { + super(source); + } + + @Override + protected void sourceChanged(ListChangeListener.Change c) { + changeList.add(c); + } + + public List> getChangeList() { + return changeList; + } + + @Override + public int getSourceIndex(int index) { + return 0; + } + + @Override + public T get(int index) { + return null; + } + + @Override + public int size() { + return 0; + } + } + + @Test + public void stringList_setFiltered_correctFilteredList() { + ObservableList listOfStrings = FXCollections.observableArrayList(); + listOfStrings.addAll("Apple", "Orange", "Pear", "Watermelon", "Strawberry", "Blueberry", "Cranberry"); + FilteredList filteredList = new FilteredList<>(listOfStrings); + assertEquals(7, filteredList.size()); + + filteredList.setPredicate(string -> string.length() == 9); + assertEquals(2, filteredList.size()); + } + + @Test + public void stringList_changeFilter_correctFilteredList() { + ObservableList listOfStrings = FXCollections.observableArrayList(); + listOfStrings.addAll("Apple", "Orange", "Pear", "Watermelon", "Strawberry", "Blueberry", "Cranberry"); + FilteredList filteredList = new FilteredList<>(listOfStrings); + assertEquals(7, filteredList.size()); + + filteredList.setPredicate(string -> StringUtil.containsIgnoreCase(string, "berry")); + assertEquals(3, filteredList.size()); + + filteredList.setPredicate(string -> StringUtil.containsIgnoreCase(string, "a")); + assertEquals(6, filteredList.size()); + } + + @Test + public void personList_changeFilter_unaffectedPersonsNotSentInChanges() { + ObservableList listOfPersons = FXCollections.observableArrayList(); + listOfPersons.addAll(TestUtil.generateSamplePersonData()); + FilteredList filteredList = new FilteredList<>(listOfPersons); + Observer observer = new Observer<>(filteredList); + List> changeList = observer.getChangeList(); + + assertEquals(9, filteredList.size()); + assertEquals(0, changeList.size()); + + filteredList.setPredicate(person -> person.getLastName().equalsIgnoreCase("mueller")); + + // correct filtered list + assertEquals(2, filteredList.size()); + assertEquals("Ruth", filteredList.get(0).getFirstName()); + assertEquals("Martin", filteredList.get(1).getFirstName()); + // a change has been added + assertEquals(1, changeList.size()); + ListChangeListener.Change firstChange = changeList.get(0); + assertTrue(firstChange.next()); + // inspect removal of 7 items + assertEquals(7, firstChange.getRemovedSize()); + assertEquals(0, firstChange.getAddedSize()); + assertFalse(firstChange.next()); + + filteredList.setPredicate(person -> person.getLastName().contains("z")); + + // correct filtered list + assertEquals(2, filteredList.size()); + assertEquals("Heinz", filteredList.get(0).getFirstName()); + assertEquals("Lydia", filteredList.get(1).getFirstName()); + // a change has been added + assertEquals(2, changeList.size()); + ListChangeListener.Change secondChange = changeList.get(1); + assertTrue(secondChange.next()); + // inspect removal of 2 items + assertEquals(2, secondChange.getRemovedSize()); + assertEquals(0, secondChange.getAddedSize()); + assertTrue(secondChange.next()); + // inspect addition of 2 items + assertEquals(0, secondChange.getRemovedSize()); + assertEquals(2, secondChange.getAddedSize()); + assertFalse(secondChange.next()); + } +} diff --git a/src/test/java/address/util/JavaVersionTest.java b/src/test/java/address/util/JavaVersionTest.java new file mode 100644 index 000000000000..1224f1983545 --- /dev/null +++ b/src/test/java/address/util/JavaVersionTest.java @@ -0,0 +1,159 @@ +package address.util; + +import address.MainApp; +import org.junit.Test; + +import static org.junit.Assert.*; + +public class JavaVersionTest { + @Test + public void javaVersionParsing_RequiredVersionString_NoExceptionThrown() { + JavaVersion.fromString(MainApp.REQUIRED_JAVA_VERSION); + } + + @Test + public void javaVersionParsing_AcceptableVersionString_ParsedVersionCorrectly() { + // Java version no build + verifyJavaVersionParsedCorrectly("1.8.0_60", 1, 8, 0, 60, 0); + // Java version with build + verifyJavaVersionParsedCorrectly("1.8.0_60-b40", 1, 8, 0, 60, 40); + // Arbitrary big version numbers no build + verifyJavaVersionParsedCorrectly("10.80.100_60", 10, 80, 100, 60, 0); + // Arbitrary big version numbers with build + verifyJavaVersionParsedCorrectly("010.080.100_60", 10, 80, 100, 60, 0); + } + + @Test(expected = IllegalArgumentException.class) + public void javaVersionParsing_JavaVersionNoUpdateNoBuild_ThrowsException() { + JavaVersion.fromString("1.8.0"); + } + + @Test(expected = IllegalArgumentException.class) + public void javaVersionParsing_RandomString_ThrowsException() { + JavaVersion.fromString("this should throw exception"); + } + + @Test + public void javaVersionToString_JavaVersion_ReturnsCorrectString() { + JavaVersion version = new JavaVersion(0, 0, 0, 0, 0); + String versionString = "0.0.0_0-b0"; + assertEquals(versionString, version.toString()); + + version = new JavaVersion(100, 200, 300, 400, 500); + versionString = "100.200.300_400-b500"; + assertEquals(versionString, version.toString()); + } + + @Test + public void javaVersionComparable_JavaVersion_HashCodesAndEqualAreCorrect() { + String version = "1.8.0_60"; + JavaVersion expectedVersion = new JavaVersion(1, 8, 0, 60, 0); + JavaVersion javaVersion = JavaVersion.fromString(version); + assertEquals(expectedVersion.hashCode(), javaVersion.hashCode()); + assertTrue(expectedVersion.equals(javaVersion)); + + version = "9.8.0_60-b10"; + expectedVersion = new JavaVersion(9, 8, 0, 60, 10); + javaVersion = JavaVersion.fromString(version); + assertEquals(expectedVersion.hashCode(), javaVersion.hashCode()); + assertTrue(expectedVersion.equals(javaVersion)); + } + + @Test + public void javaVersionComparable_JavaVersion_CompareToIsCorrect() { + JavaVersion lower, higher; + + // Tests equality + lower = new JavaVersion(0, 0, 0, 0, 0); + higher = new JavaVersion(0, 0, 0, 0, 0); + assertTrue(lower.compareTo(higher) == 0); + + lower = new JavaVersion(10, 10, 10, 10, 10); + higher = new JavaVersion(10, 10, 10, 10, 10); + assertTrue(lower.compareTo(higher) == 0); + + // Tests different build + lower = new JavaVersion(0, 0, 0, 0, 0); + higher = new JavaVersion(0, 0, 0, 0, 1); + assertTrue(lower.compareTo(higher) < 0); + assertTrue(higher.compareTo(lower) > 0); + + // Tests different update + lower = new JavaVersion(0, 0, 0, 0, 0); + higher = new JavaVersion(0, 0, 0, 2, 0); + assertTrue(lower.compareTo(higher) < 0); + assertTrue(higher.compareTo(lower) > 0); + + // Tests different minor + lower = new JavaVersion(0, 0, 0, 0, 0); + higher = new JavaVersion(0, 0, 10, 0, 0); + assertTrue(lower.compareTo(higher) < 0); + assertTrue(higher.compareTo(lower) > 0); + + // Tests different major + lower = new JavaVersion(0, 0, 0, 0, 0); + higher = new JavaVersion(0, 100, 0, 0, 0); + assertTrue(lower.compareTo(higher) < 0); + assertTrue(higher.compareTo(lower) > 0); + + // Tests different discard + lower = new JavaVersion(0, 0, 0, 0, 0); + higher = new JavaVersion(100, 0, 0, 0, 0); + assertTrue(lower.compareTo(higher) < 0); + assertTrue(higher.compareTo(lower) > 0); + + // Tests high major vs low discard + lower = new JavaVersion(0, 100, 0, 0, 0); + higher = new JavaVersion(1, 0, 0, 0, 0); + assertTrue(lower.compareTo(higher) < 0); + assertTrue(higher.compareTo(lower) > 0); + + // Tests low minor vs high major + lower = new JavaVersion(0, 0, 10, 0, 0); + higher = new JavaVersion(0, 100, 0, 0, 0); + assertTrue(lower.compareTo(higher) < 0); + assertTrue(higher.compareTo(lower) > 0); + + // Tests combinations with same discard and major, higher minor, lower update and build + lower = new JavaVersion(1, 3, 5, 6, 8); + higher = new JavaVersion(1, 3, 7, 0, 1); + assertTrue(lower.compareTo(higher) < 0); + assertTrue(higher.compareTo(lower) > 0); + } + + @Test + public void javaVersionTooLow_JavaVersion_ReturnsCorrectResult() { + JavaVersion age0 = new JavaVersion(0, 0, 0, 0, 0); + JavaVersion ageBuild1 = new JavaVersion(0, 0, 0, 0, 1); + JavaVersion ageUpdate1Build0 = new JavaVersion(0, 0, 0, 1, 0); + JavaVersion ageUpdate1Build1 = new JavaVersion(0, 0, 0, 1, 1); + JavaVersion ageMinor1 = new JavaVersion(0, 0, 1, 0, 0); + JavaVersion ageMajor1 = new JavaVersion(0, 1, 0, 0, 0); + JavaVersion ageDiscard2 = new JavaVersion(2, 0, 0, 0, 0); + + // Case version too low + assertTrue(JavaVersion.isJavaVersionLower(age0, ageBuild1)); + assertTrue(JavaVersion.isJavaVersionLower(ageBuild1, ageUpdate1Build0)); + assertTrue(JavaVersion.isJavaVersionLower(ageUpdate1Build0, ageUpdate1Build1)); + assertTrue(JavaVersion.isJavaVersionLower(ageUpdate1Build1, ageMinor1)); + assertTrue(JavaVersion.isJavaVersionLower(ageUpdate1Build1, ageMajor1)); + assertTrue(JavaVersion.isJavaVersionLower(ageMinor1, ageMajor1)); + assertTrue(JavaVersion.isJavaVersionLower(ageMajor1, ageDiscard2)); + + // Case equal + assertFalse(JavaVersion.isJavaVersionLower(age0, age0)); + assertFalse(JavaVersion.isJavaVersionLower(ageBuild1, ageBuild1)); + + // Case version higher + assertFalse(JavaVersion.isJavaVersionLower(ageUpdate1Build0, ageBuild1)); + assertFalse(JavaVersion.isJavaVersionLower(ageDiscard2, ageMajor1)); + } + + private void verifyJavaVersionParsedCorrectly(String versionString, int verDiscard, int verMajor, int verMinor, + int verUpdate, int verBuild) { + JavaVersion expectedVersion = new JavaVersion(verDiscard, verMajor, verMinor, verUpdate, verBuild); + JavaVersion javaVersion = JavaVersion.fromString(versionString); + assertEquals(expectedVersion, javaVersion); + } +} + diff --git a/src/test/java/address/util/UnmodifiableObservableListTest.java b/src/test/java/address/util/UnmodifiableObservableListTest.java new file mode 100644 index 000000000000..af4cec60b510 --- /dev/null +++ b/src/test/java/address/util/UnmodifiableObservableListTest.java @@ -0,0 +1,80 @@ +package address.util; + +import address.util.collections.UnmodifiableObservableList; +import javafx.collections.FXCollections; +import org.junit.Before; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.util.*; + +import static address.testutil.TestUtil.assertThrows; +import static org.junit.Assert.assertSame; + +public class UnmodifiableObservableListTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + List backing; + UnmodifiableObservableList list; + + @Before + public void setup() { + backing = new ArrayList<>(); + backing.add(10); + list = new UnmodifiableObservableList<>(FXCollections.observableList(backing)); + } + + @Test + public void transformationListGenerators_correctBackingList() { + assertSame(list.sorted().getSource(), list); + assertSame(list.filtered(i -> true).getSource(), list); + } + + @Test + public void mutatingMethods_disabled() { + + final Class ex = UnsupportedOperationException.class; + + assertThrows(ex, () -> list.add(0, 2)); + assertThrows(ex, () -> list.add(3)); + + assertThrows(ex, () -> list.addAll(2, 1)); + assertThrows(ex, () -> list.addAll(backing)); + assertThrows(ex, () -> list.addAll(0, backing)); + + assertThrows(ex, () -> list.set(0, 2)); + + assertThrows(ex, () -> list.setAll(new ArrayList())); + assertThrows(ex, () -> list.setAll(1, 2)); + + assertThrows(ex, () -> list.remove(0, 1)); + assertThrows(ex, () -> list.remove(null)); + assertThrows(ex, () -> list.remove(0)); + + assertThrows(ex, () -> list.removeAll(backing)); + assertThrows(ex, () -> list.removeAll(1, 2)); + + assertThrows(ex, () -> list.retainAll(backing)); + assertThrows(ex, () -> list.retainAll(1, 2)); + + assertThrows(ex, () -> list.replaceAll(i -> 1)); + + assertThrows(ex, () -> list.sort(Comparator.naturalOrder())); + + assertThrows(ex, () -> list.clear()); + + final Iterator iter = list.iterator(); + iter.next(); + assertThrows(ex, iter::remove); + + final ListIterator liter = list.listIterator(); + liter.next(); + assertThrows(ex, liter::remove); + assertThrows(ex, () -> liter.add(5)); + assertThrows(ex, () -> liter.set(3)); + assertThrows(ex, () -> list.removeIf(i -> true)); + } +} diff --git a/src/test/java/address/util/VersionTest.java b/src/test/java/address/util/VersionTest.java new file mode 100644 index 000000000000..45d101e2556a --- /dev/null +++ b/src/test/java/address/util/VersionTest.java @@ -0,0 +1,138 @@ +package address.util; + +import commons.Version; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; + +public class VersionTest { + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void versionParsing_acceptableVersionString_parsedVersionCorrectly() { + verifyVersionParsedCorrectly("V0.0.0ea", 0, 0, 0, true); + verifyVersionParsedCorrectly("V3.10.2", 3, 10, 2, false); + verifyVersionParsedCorrectly("V100.100.100ea", 100, 100, 100, true); + } + + @Test + public void versionParsing_wrongVersionString_throwIllegalArgumentException() { + thrown.expect(IllegalArgumentException.class); + Version.fromString("This is not a version string"); + } + + @Test + public void versionConstructor_correctParameter_valueAsExpected() { + Version version = new Version(19, 10, 20, true); + + assertEquals(19, version.getMajor()); + assertEquals(10, version.getMinor()); + assertEquals(20, version.getPatch()); + assertEquals(true, version.isEarlyAccess()); + } + + @Test + public void versionToString_validVersion_correctStringRepresentation() { + // boundary at 0 + Version version = new Version(0, 0, 0, true); + assertEquals("V0.0.0ea", version.toString()); + + // normal values + version = new Version(4, 10, 5, false); + assertEquals("V4.10.5", version.toString()); + + // big numbers + version = new Version(100, 100, 100, true); + assertEquals("V100.100.100ea", version.toString()); + } + + @Test + public void versionComparable_validVersion_compareToIsCorrect() { + Version one, another; + + // Tests equality + one = new Version(0, 0, 0, true); + another = new Version(0, 0, 0, true); + assertTrue(one.compareTo(another) == 0); + + one = new Version(11, 12, 13, false); + another = new Version(11, 12, 13, false); + assertTrue(one.compareTo(another) == 0); + + // Tests different patch + one = new Version(0, 0, 5, false); + another = new Version(0, 0, 0, false); + assertTrue(one.compareTo(another) > 0); + + // Tests different minor + one = new Version(0, 0, 0, false); + another = new Version(0, 5, 0, false); + assertTrue(one.compareTo(another) < 0); + + // Tests different major + one = new Version(10, 0, 0, true); + another = new Version(0, 0, 0, true); + assertTrue(one.compareTo(another) > 0); + + // Tests high major vs low minor + one = new Version(10, 0, 0, true); + another = new Version(0, 1, 0, true); + assertTrue(one.compareTo(another) > 0); + + // Tests high patch vs low minor + one = new Version(0, 0, 10, false); + another = new Version(0, 1, 0, false); + assertTrue(one.compareTo(another) < 0); + + // Tests same major minor different patch + one = new Version(2, 15, 0, false); + another = new Version(2, 15, 5, false); + assertTrue(one.compareTo(another) < 0); + + // Tests early access vs not early access on same version number + one = new Version(2, 15, 0, true); + another = new Version(2, 15, 0, false); + assertTrue(one.compareTo(another) < 0); + + // Tests early access lower version vs not early access higher version compare by version number first + one = new Version(2, 15, 0, true); + another = new Version(2, 15, 5, false); + assertTrue(one.compareTo(another) < 0); + + // Tests early access higher version vs not early access lower version compare by version number first + one = new Version(2, 15, 0, false); + another = new Version(2, 15, 5, true); + assertTrue(one.compareTo(another) < 0); + } + + @Test + public void versionComparable_validVersion_hashCodeIsCorrect() { + Version version = new Version(100, 100, 100, true); + assertEquals(100100100, version.hashCode()); + + version = new Version(10, 10, 10, false); + assertEquals(1010010010, version.hashCode()); + } + + @Test + public void versionComparable_validVersion_equalIsCorrect() { + Version one, another; + + one = new Version(0, 0, 0, false); + another = new Version(0, 0, 0, false); + assertTrue(one.equals(another)); + + one = new Version(100, 191, 275, true); + another = new Version(100, 191, 275, true); + assertTrue(one.equals(another)); + } + + private void verifyVersionParsedCorrectly(String versionString, + int major, int minor, int patch, boolean isEarlyAccess) { + assertEquals(new Version(major, minor, patch, isEarlyAccess), Version.fromString(versionString)); + } +} diff --git a/src/test/java/commons/FileUtilTest.java b/src/test/java/commons/FileUtilTest.java new file mode 100644 index 000000000000..55d094d3adc6 --- /dev/null +++ b/src/test/java/commons/FileUtilTest.java @@ -0,0 +1,31 @@ +package commons; + + +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import java.io.File; + +import static org.junit.Assert.assertEquals; + +public class FileUtilTest { + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void getPath(){ + + // valid case + assertEquals("folder" + File.separator + "sub-folder", FileUtil.getPath("folder/sub-folder")); + + // null parameter -> assertion failure + thrown.expect(AssertionError.class); + FileUtil.getPath(null); + + // no forwards slash -> assertion failure + thrown.expect(AssertionError.class); + FileUtil.getPath("folder"); + } +} diff --git a/src/test/java/commons/JsonUtilTest.java b/src/test/java/commons/JsonUtilTest.java new file mode 100644 index 000000000000..d60d8e9543ee --- /dev/null +++ b/src/test/java/commons/JsonUtilTest.java @@ -0,0 +1,100 @@ +package commons; + +import address.model.datatypes.AddressBook; +import address.model.datatypes.ReadOnlyAddressBook; +import address.model.datatypes.person.Person; +import address.model.datatypes.person.ReadOnlyPerson; +import address.model.datatypes.tag.Tag; +import address.storage.StorageAddressBook; +import org.junit.Test; + +import java.io.IOException; +import java.time.LocalDate; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import static org.junit.Assert.assertEquals; + +/** + * Tests JSON Read and Write + */ +public class JsonUtilTest { + + @Test + public void jsonUtil_readJsonStringToObjectInstance_correctObject() throws IOException { + String jsonString = "{\n" + + " \"persons\" : [ {\n" + + " \"id\" : 1,\n" + + " \"firstName\" : \"First\",\n" + + " \"lastName\" : \"Last\",\n" + + " \"street\" : \"\",\n" + + " \"postalCode\" : \"123456\",\n" + + " \"city\" : \"Singapore\",\n" + + " \"githubUsername\" : \"FirstLast\",\n" + + " \"birthday\" : \"1980-03-18\",\n" + + " \"tags\" : [ {\n" + + " \"name\" : \"Tag\"\n" + + " } ],\n" + + " \"birthday\" : \"1980-03-18\"\n" + + " } ],\n" + + " \"tags\" : [ {\n" + + " \"name\" : \"Tag\"\n" + + " } ]\n" + + "}"; + ReadOnlyAddressBook addressBook = JsonUtil.fromJsonString(jsonString, StorageAddressBook.class); + assertEquals(1, addressBook.getPersonList().size()); + assertEquals(1, addressBook.getTagList().size()); + + ReadOnlyPerson person = addressBook.getPersonList().get(0); + Tag tag = addressBook.getTagList().get(0); + + assertEquals(1, person.getId()); + assertEquals("First", person.getFirstName()); + assertEquals("Last", person.getLastName()); + assertEquals("Singapore", person.getCity()); + assertEquals("123456", person.getPostalCode()); + assertEquals(tag, person.getTagList().get(0)); + assertEquals("Tag", person.getTagList().get(0).getName()); + assertEquals(LocalDate.of(1980, 3, 18), person.getBirthday()); + assertEquals("FirstLast", person.getGithubUsername()); + } + + @Test + public void jsonUtil_writeThenReadObjectToJson_correctObject() throws IOException { + // Write + Tag sampleTag = new Tag("Tag"); + Person samplePerson = new Person("First", "Last", 1); + samplePerson.setCity("Singapore"); + samplePerson.setPostalCode("123456"); + List tag = new ArrayList<>(); + tag.add(sampleTag); + samplePerson.setTags(tag); + samplePerson.setBirthday(LocalDate.of(1980, 3, 18)); + samplePerson.setGithubUsername("FirstLast"); + + AddressBook addressBook = new AddressBook(); + addressBook.setPersons(Arrays.asList(samplePerson)); + addressBook.setTags(Arrays.asList(sampleTag)); + + String jsonString = JsonUtil.toJsonString(new StorageAddressBook(addressBook)); + + // Read + ReadOnlyAddressBook addressBookRead = JsonUtil.fromJsonString(jsonString, StorageAddressBook.class); + assertEquals(1, addressBookRead.getPersonList().size()); + assertEquals(1, addressBookRead.getTagList().size()); + + ReadOnlyPerson person = addressBookRead.getPersonList().get(0); + Tag tagRead = addressBookRead.getTagList().get(0); + + assertEquals(1, person.getId()); + assertEquals("First", person.getFirstName()); + assertEquals("Last", person.getLastName()); + assertEquals("Singapore", person.getCity()); + assertEquals("123456", person.getPostalCode()); + assertEquals(tagRead, person.getTagList().get(0)); + assertEquals("Tag", person.getTagList().get(0).getName()); + assertEquals(LocalDate.of(1980, 3, 18), person.getBirthday()); + assertEquals("FirstLast", person.getGithubUsername()); + } +} diff --git a/src/test/java/commons/UrlUtilTest.java b/src/test/java/commons/UrlUtilTest.java new file mode 100644 index 000000000000..86cdb20e5532 --- /dev/null +++ b/src/test/java/commons/UrlUtilTest.java @@ -0,0 +1,43 @@ +package commons; + +import org.junit.Test; + +import java.net.MalformedURLException; +import java.net.URL; + +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; + +/** + * Tests the UrlUtil methods. + */ +public class UrlUtilTest { + + @Test + public void testCompareBaseUrls_differentCapital_success() throws MalformedURLException { + URL url1 = new URL("https://www.Google.com/a"); + URL url2 = new URL("https://www.google.com/A"); + assertTrue(UrlUtil.compareBaseUrls(url1, url2)); + } + + @Test + public void testCompareBaseUrls_testWithAndWithoutWww_success() throws MalformedURLException { + URL url1 = new URL("https://google.com/a"); + URL url2 = new URL("https://www.google.com/a"); + assertTrue(UrlUtil.compareBaseUrls(url1, url2)); + } + + @Test + public void testCompareBaseUrls_differentSlashes_success() throws MalformedURLException { + URL url1 = new URL("https://www.Google.com/a/acb/"); + URL url2 = new URL("https://www.google.com/A/acb"); + assertTrue(UrlUtil.compareBaseUrls(url1, url2)); + } + + @Test + public void testCompareBaseUrls_differentUrl_fail() throws MalformedURLException { + URL url1 = new URL("https://www.Google.com/a/ac_b/"); + URL url2 = new URL("https://www.google.com/A/acb"); + assertFalse(UrlUtil.compareBaseUrls(url1, url2)); + } +} diff --git a/src/test/java/commons/XmlUtilTest.java b/src/test/java/commons/XmlUtilTest.java new file mode 100644 index 000000000000..781eb6dcf3f7 --- /dev/null +++ b/src/test/java/commons/XmlUtilTest.java @@ -0,0 +1,93 @@ +package commons; + +import address.model.datatypes.AddressBook; +import address.storage.StorageAddressBook; +import address.testutil.AddressBookBuilder; +import address.testutil.TestUtil; +import org.junit.Rule; +import org.junit.Test; +import org.junit.rules.ExpectedException; + +import javax.xml.bind.JAXBException; +import java.io.File; +import java.io.FileNotFoundException; + +import static org.junit.Assert.assertEquals; + +public class XmlUtilTest { + + private static final String TEST_DATA_FOLDER = FileUtil.getPath("src/test/data/XmlUtilTest/"); + private static final File EMPTY_FILE = new File(TEST_DATA_FOLDER + "empty.xml"); + private static final File MISSING_FILE = new File(TEST_DATA_FOLDER + "missing.xml"); + private static final File VALID_FILE = new File(TEST_DATA_FOLDER + "validAddressBook.xml"); + private static final File TEMP_FILE = new File(TestUtil.appendToSandboxPath("tempAddressBook.xml")); + + @Rule + public ExpectedException thrown = ExpectedException.none(); + + @Test + public void getDataFromFile_nullFile_AssertionError() throws Exception { + thrown.expect(AssertionError.class); + XmlUtil.getDataFromFile(null, AddressBook.class); + } + + @Test + public void getDataFromFile_nullClass_AssertionError() throws Exception { + thrown.expect(AssertionError.class); + XmlUtil.getDataFromFile(VALID_FILE, null); + } + + @Test + public void getDataFromFile_missingFile_FileNotFoundException() throws Exception { + thrown.expect(FileNotFoundException.class); + XmlUtil.getDataFromFile(MISSING_FILE, AddressBook.class); + } + + @Test + public void getDataFromFile_emptyFile_DataFormatMismatchException() throws Exception { + thrown.expect(JAXBException.class); + XmlUtil.getDataFromFile(EMPTY_FILE, AddressBook.class); + } + + @Test + public void getDataFromFile_validFile_validResult() throws Exception { + StorageAddressBook dataFromFile = XmlUtil.getDataFromFile(VALID_FILE, StorageAddressBook.class); + assertEquals(2, dataFromFile.getPersonList().size()); + assertEquals(1, dataFromFile.getTagList().size()); + } + + @Test + public void saveDataToFile_nullFile_AssertionError() throws Exception { + thrown.expect(AssertionError.class); + XmlUtil.saveDataToFile(null, new AddressBook()); + } + + @Test + public void saveDataToFile_nullClass_AssertionError() throws Exception { + thrown.expect(AssertionError.class); + XmlUtil.saveDataToFile(VALID_FILE, null); + } + + @Test + public void saveDataToFile_missingFile_FileNotFoundException() throws Exception { + thrown.expect(FileNotFoundException.class); + XmlUtil.saveDataToFile(MISSING_FILE, new AddressBook()); + } + + @Test + public void saveDataToFile_validFile_dataSaved() throws Exception { + TEMP_FILE.createNewFile(); + StorageAddressBook dataToWrite = new StorageAddressBook(new AddressBook()); + XmlUtil.saveDataToFile(TEMP_FILE, dataToWrite); + StorageAddressBook dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, StorageAddressBook.class); + assertEquals((new AddressBook(dataToWrite)).toString(),(new AddressBook(dataFromFile)).toString()); + //TODO: use equality instead of string comparisons + + AddressBookBuilder builder = new AddressBookBuilder(new AddressBook()); + dataToWrite = new StorageAddressBook(builder.withPerson("John", "Doe").withTag("Friends").build()); + + XmlUtil.saveDataToFile(TEMP_FILE, dataToWrite); + dataFromFile = XmlUtil.getDataFromFile(TEMP_FILE, StorageAddressBook.class); + assertEquals((new AddressBook(dataToWrite)).toString(),(new AddressBook(dataFromFile)).toString()); + } +} diff --git a/src/test/java/guitests/FilterPersonsGuiTest.java b/src/test/java/guitests/FilterPersonsGuiTest.java new file mode 100644 index 000000000000..5f9798068b2d --- /dev/null +++ b/src/test/java/guitests/FilterPersonsGuiTest.java @@ -0,0 +1,142 @@ +package guitests; + +import address.model.datatypes.AddressBook; +import address.model.datatypes.person.Person; +import address.model.datatypes.person.ReadOnlyPerson; +import address.model.datatypes.tag.Tag; +import address.testutil.PersonBuilder; +import commons.StringUtil; +import org.junit.Test; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertTrue; + +public class FilterPersonsGuiTest extends GuiTestBase { + private Person sameLastNameAsAlice, sameCityAsAlice; + + @Override + public AddressBook getInitialData() { + AddressBook addressBook = new AddressBook(td.book); + + sameLastNameAsAlice = new Person("Zack", td.alice.getLastName(), addressBook.getPersonList().size() + 1); + sameCityAsAlice = new PersonBuilder(new Person("Yoshi", "Nakamoto", addressBook.getPersonList().size() + 2)) + .withCity(td.alice.getCity()) + .build(); + + addressBook.addPerson(sameLastNameAsAlice); + addressBook.addPerson(sameCityAsAlice); + + return addressBook; + } + + @Test + public void filterPersons_singleSimpleQualifier() { + personListPanel.enterFilterAndApply("tag:colleagues"); + assertResultList(filterByTag(getInitialData().getPersonList(), "colleagues", false)); + } + + @Test + public void filterPersons_multipleSimpleQualifiers() { + personListPanel.enterFilterAndApply("tag:friends city:new"); + assertResultList(filterByCity(filterByTag(getInitialData().getPersonList(), "friends", false), "new", false)); + + personListPanel.enterFilterAndApply("tag:friends name:brown"); + assertResultList( + filterByTag(filterByName(getInitialData().getPersonList(), "brown", false), "friends", false)); + + personListPanel.enterFilterAndApply("name:edwards name:brown id:6"); + assertResultList( + filterById(filterByName(filterByName(getInitialData().getPersonList(), "edwards", false), + "brown", false), 6, false)); + + personListPanel.enterFilterAndApply("name:edwa name:dan id:6"); + assertResultList( + filterById(filterByName(filterByName(getInitialData().getPersonList(), "edwa", false), + "dan", false), 6, false)); + } + + @Test + public void filterPersons_unknownTag() { + personListPanel.enterFilterAndApply("tag:enemies"); + assertResultList(filterByTag(getInitialData().getPersonList(), "enemies", false)); + } + + @Test + public void filterPersons_negatedQualifiers() { + personListPanel.enterFilterAndApply("!tag:enemies"); + assertResultList(filterByTag(getInitialData().getPersonList(), "enemies", true)); + + personListPanel.enterFilterAndApply("!!tag:friends"); + assertResultList(filterByTag(getInitialData().getPersonList(), "friends", false)); + + personListPanel.enterFilterAndApply("!!!city:Texas"); + assertResultList(filterByCity(getInitialData().getPersonList(), "Texas", true)); + } + + @Test + public void filterPersons_multipleQualifiers() { + personListPanel.enterFilterAndApply("tag:friends !city:california"); + assertResultList( + filterByCity(filterByTag(getInitialData().getPersonList(), "friends", false), "california", true)); + + personListPanel.enterFilterAndApply("!tag:colleagues city:chic"); + assertResultList( + filterByCity(filterByTag(getInitialData().getPersonList(), "colleagues", true), "chic", false)); + + personListPanel.enterFilterAndApply("id:2 !!!id:3 !id:5 !!!!!id:8"); + assertResultList( + filterById(filterById(filterById(filterById(getInitialData().getPersonList(), 2, false), 3, true), + 5, true), 8, true)); + + personListPanel.enterFilterAndApply("id:2 id:3 id:5 !id:4"); + assertResultList( + filterById(filterById(filterById(filterById(getInitialData().getPersonList(), 2, false), 3, false), + 5, false), 4, true)); + } + + private List filterByTag(List originalList, String partialTagName, + boolean isNegative) { + return originalList.stream() + .filter(person -> isNegative != hasTag(person, partialTagName)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private List filterByName(List originalList, String partialName, + boolean isNegative) { + return originalList.stream() + .filter(person -> isNegative != hasName(person, partialName)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private List filterById(List originalList, int id, boolean isNegative) { + return originalList.stream() + .filter(person -> isNegative != (person.getId() == id)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private List filterByCity(List originalList, String partialCityName, + boolean isNegative) { + return originalList.stream() + .filter(person -> isNegative != StringUtil.containsIgnoreCase(person.getCity(), partialCityName)) + .collect(Collectors.toCollection(ArrayList::new)); + } + + private boolean hasName(ReadOnlyPerson person, String partialName) { + return StringUtil.containsIgnoreCase(person.getFirstName(), partialName) + || StringUtil.containsIgnoreCase(person.getLastName(), partialName); + } + + private boolean hasTag(ReadOnlyPerson person, String partialTagName) { + for (Tag tag : person.getTagList()) { + if (StringUtil.containsIgnoreCase(tag.getName(), partialTagName)) return true; + } + return false; + } + + private void assertResultList(List resultList) { + assertTrue(personListPanel.containsListOnly(resultList)); + } +} diff --git a/src/test/java/guitests/FullSystemTest.java b/src/test/java/guitests/FullSystemTest.java new file mode 100644 index 000000000000..6264e5734f40 --- /dev/null +++ b/src/test/java/guitests/FullSystemTest.java @@ -0,0 +1,12 @@ +package guitests; + +import org.junit.Test; + + +public class FullSystemTest extends GuiTestBase { + + @Test + public void scenarioOne() { + + } +} diff --git a/src/test/java/guitests/GuiRobot.java b/src/test/java/guitests/GuiRobot.java new file mode 100644 index 000000000000..cd67f1a3f756 --- /dev/null +++ b/src/test/java/guitests/GuiRobot.java @@ -0,0 +1,44 @@ +package guitests; + +import address.testutil.TestUtil; +import javafx.scene.input.KeyCode; +import javafx.scene.input.KeyCodeCombination; +import javafx.scene.input.MouseButton; +import org.testfx.api.FxRobot; + +/** + * Robot used to simulate user actions on the GUI. + * Extends {@link FxRobot} by adding some customized functionality and workarounds. + */ +public class GuiRobot extends FxRobot { + + public GuiRobot push(KeyCode... keyCodes){ + return (GuiRobot) super.push(TestUtil.scrub(keyCodes)); + } + + public GuiRobot push(KeyCodeCombination keyCodeCombination){ + return (GuiRobot) super.push(TestUtil.scrub(keyCodeCombination)); + } + + public GuiRobot press(KeyCode... keyCodes) { + return (GuiRobot) super.press(TestUtil.scrub(keyCodes)); + } + + public GuiRobot release(KeyCode... keyCodes) { + return (GuiRobot) super.release(TestUtil.scrub(keyCodes)); + } + + public GuiRobot type(KeyCode... keyCodes) { + return (GuiRobot) super.type(TestUtil.scrub(keyCodes)); + } + + @Override + public GuiRobot clickOn(String query, MouseButton... buttons) { + return (GuiRobot) super.clickOn(query, buttons); + } + + @Override + public GuiRobot drag(String query, MouseButton... buttons) { + return (GuiRobot) super.drag(query, buttons); + } +} diff --git a/src/test/java/guitests/GuiTestBase.java b/src/test/java/guitests/GuiTestBase.java new file mode 100644 index 000000000000..10248925d030 --- /dev/null +++ b/src/test/java/guitests/GuiTestBase.java @@ -0,0 +1,118 @@ +package guitests; + +import address.TestApp; +import address.events.EventManager; +import address.model.datatypes.AddressBook; +import address.model.datatypes.person.Person; +import address.testutil.ScreenShotRule; +import address.testutil.TestUtil; +import address.testutil.TypicalTestData; +import address.util.Config; +import guitests.guihandles.*; +import javafx.stage.Stage; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.junit.Rule; +import org.junit.rules.TestName; +import org.loadui.testfx.GuiTest; +import org.testfx.api.FxToolkit; + +import java.io.File; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; + +import static org.junit.Assert.assertTrue; + +public class GuiTestBase { + + @Rule + public ScreenShotRule screenShotRule = new ScreenShotRule(); + + @Rule + public TestName name = new TestName(); + + TestApp testApp; + + /* Handles to GUI elements present at the start up are created in advance + * for easy access from child classes. + */ + protected MainGuiHandle mainGui; + protected MainMenuHandle mainMenu; + protected PersonListPanelHandle personListPanel; + protected HeaderStatusBarHandle statusBar; + protected TypicalTestData td = new TypicalTestData(); + private Stage stage; + + + @BeforeClass + public static void setupSpec() { + try { + FxToolkit.registerPrimaryStage(); + FxToolkit.hideStage(); + } catch (TimeoutException e) { + e.printStackTrace(); + } + } + + @Before + public void setup() throws Exception { + FxToolkit.setupStage((stage) -> { + mainGui = new MainGuiHandle(new GuiRobot(), stage); + mainMenu = mainGui.getMainMenu(); + personListPanel = mainGui.getPersonListPanel(); + statusBar = mainGui.getStatusBar(); + this.stage = stage; + }); + EventManager.clearSubscribers(); + testApp = (TestApp) FxToolkit.setupApplication(() -> new TestApp(this::getInitialData, getDataFileLocation())); + FxToolkit.showStage(); + while(!stage.isShowing()); + mainGui.focusOnMainApp(); + } + + /** + * Override this in child classes to set the initial local data. + * Return null to use the data in the file specified in {@link #getDataFileLocation()} + */ + protected AddressBook getInitialData() { + return TestUtil.generateSampleAddressBook(); + } + + /** + * Override this in child classes to set the data file location. + * @return + */ + protected String getDataFileLocation() { + return TestApp.SAVE_LOCATION_FOR_TESTING; + } + + public Config getTestingConfig() { + return testApp.getTestingConfig(); + } + + @After + public void cleanup() throws TimeoutException { + File file = GuiTest.captureScreenshot(); + TestUtil.renameFile(file, this.getClass().getName() + name.getMethodName() + ".png"); + FxToolkit.cleanupStages(); + } + + public void sleep(long duration, TimeUnit timeunit) { + mainGui.sleep(duration, timeunit); + } + + public void sleepForGracePeriod() { + mainGui.sleepForGracePeriod(); + } + + public void sleepUntilNextSync() { + //TODO: actively check for sync status rather than sleep for a fixed time + //sleep(getTestingConfig().getUpdateInterval(), TimeUnit.MILLISECONDS); + } + + public void assertMatching(PersonCardHandle card, Person person) { + assertTrue(TestUtil.compareCardAndPerson(card, person)); + } + +} diff --git a/src/test/java/guitests/guihandles/GuiHandle.java b/src/test/java/guitests/guihandles/GuiHandle.java new file mode 100644 index 000000000000..548c048581e9 --- /dev/null +++ b/src/test/java/guitests/guihandles/GuiHandle.java @@ -0,0 +1,162 @@ +package guitests.guihandles; + +import address.TestApp; +import address.model.datatypes.person.Person; +import address.util.AppLogger; +import address.util.LoggerManager; +import guitests.GuiRobot; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.TextField; +import javafx.scene.input.KeyCode; +import javafx.stage.Stage; +import javafx.stage.Window; + +import java.lang.reflect.Constructor; + +/** + * Base class for all GUI Handles used in testing. + */ +public class GuiHandle { + protected final GuiRobot guiRobot; + protected final Stage primaryStage; + protected final String stageTitle; + + private final AppLogger logger = LoggerManager.getLogger(this.getClass()); + + public GuiHandle(GuiRobot guiRobot, Stage primaryStage, String stageTitle) { + this.guiRobot = guiRobot; + this.primaryStage = primaryStage; + this.stageTitle = stageTitle; + focusOnSelf(); + } + + /** + * Creates an object of the specified GuiHandle child class. + */ + public T as(Class clazz) { + try { + Constructor ctor = clazz.getConstructor(GuiRobot.class, Stage.class); + Object object = ctor.newInstance(new Object[] { guiRobot, primaryStage}); + return (T) object; + } catch (Exception e) { + throw new RuntimeException("Cannot create gui handle of type " + clazz.getName(), e); + } + } + + public void write(String textToWrite) { + guiRobot.write(textToWrite); + } + + public void focusOnWindow(String stageTitle) { + logger.info("Focusing {}", stageTitle); + java.util.Optional window = guiRobot.listTargetWindows() + .stream() + .filter(w -> w instanceof Stage && ((Stage) w).getTitle().equals(stageTitle)).findAny(); + + if (!window.isPresent()) { + logger.warn("Can't find stage {}, Therefore, aborting focusing", stageTitle); + return; + } + + guiRobot.targetWindow(window.get()); + guiRobot.interact(() -> window.get().requestFocus()); + logger.info("Finishing focus {}", stageTitle); + } + + protected Node getNode(String query) { + return guiRobot.lookup(query).tryQuery().get(); + } + + protected String getTextFieldText(String filedName) { + return ((TextField) getNode(filedName)).getText(); + } + + public void moveCursor(Person person) { + guiRobot.moveTo(person.getFirstName()); + } + + /** + * Sets the specified text field directly (as opposed to simulating the user typing). + * @param textFieldId + * @param newText + */ + protected void setTextField(String textFieldId, String newText) { + TextField textField = (TextField) getNode(textFieldId); + textField.setText(newText); + } + + /** + * Simulates the user typing text in the given text field + * @param textFieldId + * @param newText + */ + protected void typeTextField(String textFieldId, String newText) { + guiRobot.clickOn(textFieldId); + guiRobot.push(KeyCode.SHORTCUT, KeyCode.A).eraseText(1) + .write(newText); + } + + public void pressEnter() { + guiRobot.type(KeyCode.ENTER).sleep(500); + } + + public void pressTab() { + guiRobot.push(KeyCode.TAB).sleep(500); + } + + protected void pressEsc() { + guiRobot.push(KeyCode.ESCAPE); + } + + /** + * Presses the button with the name 'Cancel' on it. + */ + public void clickCancel() { + guiRobot.clickOn("Cancel"); + } + + /** + * Presses the button named 'OK'. + */ + public void clickOk() { + guiRobot.clickOn("OK"); + } + + public GuiHandle clickOn(String id) { + guiRobot.clickOn(id); + return this; + } + + public GuiHandle rightClickOn(String id) { + guiRobot.rightClickOn(id); + return this; + } + + /** + * Dismisses the dialog by pressing Esc + */ + public void dismiss() { + pressEsc(); + } + + public void dismissErrorMessage(String errorDialogTitle) { + focusOnWindow(errorDialogTitle); + clickOk(); + focusOnSelf(); + } + + protected String getTextFromLabel(String fieldId, Node parentNode) { + return ((Label) guiRobot.from(parentNode).lookup(fieldId).tryQuery().get()).getText(); + } + + public void focusOnSelf() { + if (stageTitle != null) { + focusOnWindow(stageTitle); + } + } + + public void focusOnMainApp() { + this.focusOnWindow(TestApp.APP_TITLE); + } +} diff --git a/src/test/java/guitests/guihandles/HeaderStatusBarHandle.java b/src/test/java/guitests/guihandles/HeaderStatusBarHandle.java new file mode 100644 index 000000000000..ebd76c7bdf04 --- /dev/null +++ b/src/test/java/guitests/guihandles/HeaderStatusBarHandle.java @@ -0,0 +1,25 @@ +package guitests.guihandles; + +import address.TestApp; +import address.controller.StatusBarHeaderController; +import guitests.GuiRobot; +import javafx.stage.Stage; +import org.controlsfx.control.StatusBar; + +/** + * A handler for the HeaderStatusBar of the UI + */ +public class HeaderStatusBarHandle extends GuiHandle { + + public HeaderStatusBarHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, TestApp.APP_TITLE); + } + + public String getText() { + return getStatusBar().getText(); + } + + private StatusBar getStatusBar() { + return (StatusBar) getNode("#" + StatusBarHeaderController.HEADER_STATUS_BAR_ID); + } +} diff --git a/src/test/java/guitests/guihandles/MainGuiHandle.java b/src/test/java/guitests/guihandles/MainGuiHandle.java new file mode 100644 index 000000000000..bf2ce197fe6c --- /dev/null +++ b/src/test/java/guitests/guihandles/MainGuiHandle.java @@ -0,0 +1,57 @@ +package guitests.guihandles; + +import address.TestApp; +import address.model.ModelManager; +import commons.OsDetector; +import guitests.GuiRobot; +import javafx.stage.Stage; +import org.testfx.api.FxRobot; + +import java.util.concurrent.TimeUnit; + +/** + * Provides a handle for the main GUI. + */ +public class MainGuiHandle extends GuiHandle { + + public MainGuiHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, TestApp.APP_TITLE); + } + + public PersonListPanelHandle getPersonListPanel(){ + return new PersonListPanelHandle(guiRobot, primaryStage); + } + + public MainMenuHandle getMainMenu() { + return new MainMenuHandle(guiRobot, primaryStage); + } + + public HeaderStatusBarHandle getStatusBar() { + return new HeaderStatusBarHandle(guiRobot, primaryStage); + } + + public boolean isMinimized() { + return primaryStage.isIconified() && !primaryStage.isMaximized(); + } + + public boolean isMaximized() { + return primaryStage.isMaximized() && !primaryStage.isIconified(); + } + + public boolean isDefaultSize() { + if (OsDetector.isOnMac()) { + return !primaryStage.isIconified(); // TODO: Find a way to verify this on mac since isMaximized is always true + } else { + return !primaryStage.isMaximized() && !primaryStage.isIconified(); + } + } + + public FxRobot sleepForGracePeriod() { + return guiRobot.sleep((ModelManager.GRACE_PERIOD_DURATION + 1), TimeUnit.SECONDS); + } + + public FxRobot sleep(long duration, TimeUnit timeunit) { + return guiRobot.sleep(duration, timeunit); + } + +} diff --git a/src/test/java/guitests/guihandles/MainMenuHandle.java b/src/test/java/guitests/guihandles/MainMenuHandle.java new file mode 100644 index 000000000000..922d64f6086d --- /dev/null +++ b/src/test/java/guitests/guihandles/MainMenuHandle.java @@ -0,0 +1,21 @@ +package guitests.guihandles; + +import address.TestApp; +import guitests.GuiRobot; +import javafx.stage.Stage; + +import java.util.Arrays; + +/** + * Provides a handle to the main menu of the app. + */ +public class MainMenuHandle extends GuiHandle { + public MainMenuHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, TestApp.APP_TITLE); + } + + public GuiHandle clickOn(String... menuText) { + Arrays.stream(menuText).forEach((menuItem) -> guiRobot.clickOn(menuItem)); + return this; + } +} diff --git a/src/test/java/guitests/guihandles/ManageTagsDialogHandle.java b/src/test/java/guitests/guihandles/ManageTagsDialogHandle.java new file mode 100644 index 000000000000..e59749add502 --- /dev/null +++ b/src/test/java/guitests/guihandles/ManageTagsDialogHandle.java @@ -0,0 +1,43 @@ +package guitests.guihandles; + +import address.controller.MainController; +import guitests.GuiRobot; +import javafx.collections.ObservableList; +import javafx.scene.Node; +import javafx.scene.control.Label; +import javafx.scene.control.ScrollPane; +import javafx.scene.layout.VBox; +import javafx.stage.Stage; + +import java.util.ArrayList; +import java.util.List; +import java.util.stream.Collectors; + +/** + * Provides a handle to the dialog used for managing tags. + */ +public class ManageTagsDialogHandle extends GuiHandle { + + public static final String EDIT_TAG_TEXT_FIELD = "#tagNameField"; + public static final String TAG_LIST_SCROLL_PANE_FIELD_ID = "#tagListScrollPane"; + public static final String TAG_NAME_FIELD_ID = "#tagName"; + + public ManageTagsDialogHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, MainController.DIALOG_TITLE_TAG_LIST); + } + + private ScrollPane getScrollPane() { + return (ScrollPane) getNode(TAG_LIST_SCROLL_PANE_FIELD_ID); + } + + public List getTagNames() { + ObservableList childrenUnmodifiable = ((VBox) getScrollPane().getContent()).getChildren(); + return childrenUnmodifiable.stream().map(n -> ((Label) n.lookup(TAG_NAME_FIELD_ID)).getText()) + .collect(Collectors.toCollection(ArrayList::new)); + } + + public boolean contains(String value) { + return getTagNames().stream().filter(value::equals).findAny().isPresent(); + } + +} diff --git a/src/test/java/guitests/guihandles/PersonCardHandle.java b/src/test/java/guitests/guihandles/PersonCardHandle.java new file mode 100644 index 000000000000..27528f12037e --- /dev/null +++ b/src/test/java/guitests/guihandles/PersonCardHandle.java @@ -0,0 +1,134 @@ +package guitests.guihandles; + +import address.controller.PersonCardController; +import address.model.datatypes.person.Person; +import address.model.datatypes.tag.Tag; +import address.testutil.PersonBuilder; +import guitests.GuiRobot; +import javafx.scene.Node; +import javafx.stage.Stage; + +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * Provides a handle to a person card in the person list panel. + */ +public class PersonCardHandle extends GuiHandle { + private static final String FIRST_NAME_FIELD_ID = "#firstName"; + private static final String LAST_NAME_FIELD_ID = "#lastName"; + private static final String ADDRESS_FIELD_ID = "#address"; + private static final String BIRTHDAY_FIELD_ID = "#birthday"; + private static final String TAGS_FIELD_ID = "#tags"; + private static final String PENDING_STATE_LABEL_FIELD_ID = "#commandTypeLabel"; + private static final String PENDING_STATE_PROGRESS_INDICATOR_FIELD_ID = "#remoteRequestOngoingIndicator"; + private static final String PENDING_STATE_ROOT_FIELD_ID = "#commandStateDisplayRootNode"; + private static final String PENDING_STATE_GRACE_PERIOD_FIELD_ID = "#commandStateInfoLabel"; + + private Node node; + + public PersonCardHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, null); + } + + public PersonCardHandle(GuiRobot guiRobot, Stage primaryStage, Node node){ + super(guiRobot, primaryStage, null); + this.node = node; + } + + public String getLastName(){ + return getTextFromLabel(LAST_NAME_FIELD_ID); + } + + protected String getTextFromLabel(String fieldId) { + return getTextFromLabel(fieldId, node); + } + + public String getFirstName() { + return getTextFromLabel(FIRST_NAME_FIELD_ID); + } + + public String getAddress() { + return getTextFromLabel(ADDRESS_FIELD_ID); + } + + public String getBirthday() { + return getTextFromLabel(BIRTHDAY_FIELD_ID); + } + + public String getTags() { + return getTextFromLabel(TAGS_FIELD_ID); + } + + public boolean isSamePerson(Person person){ + return getFirstName().equals(person.getFirstName()) + && getLastName().equals(person.getLastName()) + && getAddress().equals(PersonCardController.getAddressString(person.getStreet(), + person.getCity(), person.getPostalCode())); + } + + public Tag[] getTagList() { + String tags = getTags(); + + if (tags.equals("")) { + return new Tag[]{}; + } + + final String[] split = tags.split(", "); + + final List collect = Arrays.asList(split).stream().map(e -> new Tag(e.replaceFirst("Tag: ", ""))).collect(Collectors.toList()); + + return collect.toArray(new Tag[split.length]); + } + + @Override + public boolean equals(Object obj) { + if(obj instanceof PersonCardHandle) { + PersonCardHandle handle = (PersonCardHandle) obj; + return getFirstName().equals(handle.getFirstName()) && getLastName().equals(handle.getLastName()) + && getAddress().equals(handle.getAddress()) && getBirthday().equals(handle.getBirthday()); + } + return super.equals(obj); + } + + private int getRemainingGracePeriod() { + return Integer.valueOf(getTextFromLabel(PENDING_STATE_GRACE_PERIOD_FIELD_ID)); + } + + public boolean isGracePeriodFrozen() { + int gracePeriod = getRemainingGracePeriod(); + guiRobot.sleep(2, TimeUnit.SECONDS); + return gracePeriod == getRemainingGracePeriod(); + } + + /** + * Gets the representation of this card in a person form. + * @param id The id of the person + * @param githubUsername The github username of the person + * @return + */ + public Person mockPerson(int id, String githubUsername) { + String address = getAddress(); + final String[] split = address.split(System.lineSeparator()); + String street = "", city = "", postalcode = ""; + for(int i = 0; i < split.length; i++) { + if (i == 0) { + street = split[i]; + } else if (i == 1) { + city = split[i]; + } else if (i == 2) { + postalcode = split[i]; + } + } + return new PersonBuilder(getFirstName(), getLastName(), id).withStreet(street).withCity(city) + .withPostalCode(postalcode).withBirthday(getBirthday()).withGithubUsername(githubUsername) + .withTags(getTagList()).build(); + } + + @Override + public String toString() { + return getFirstName() + " " + getLastName() + " " + getAddress() + " " + getBirthday(); + } +} diff --git a/src/test/java/guitests/guihandles/PersonListPanelHandle.java b/src/test/java/guitests/guihandles/PersonListPanelHandle.java new file mode 100644 index 000000000000..e163f2b580f1 --- /dev/null +++ b/src/test/java/guitests/guihandles/PersonListPanelHandle.java @@ -0,0 +1,392 @@ +package guitests.guihandles; + + +import address.TestApp; +import address.model.datatypes.person.Person; +import address.model.datatypes.person.ReadOnlyPerson; +import address.testutil.TestUtil; +import guitests.GuiRobot; +import javafx.collections.ObservableList; +import javafx.event.Event; +import javafx.geometry.Point2D; +import javafx.scene.Node; +import javafx.scene.control.ListView; +import javafx.scene.input.ContextMenuEvent; +import javafx.scene.input.KeyCode; +import javafx.scene.input.PickResult; +import javafx.stage.Stage; +import javafx.stage.Window; + +import java.util.*; +import java.util.stream.Collectors; + +import static org.junit.Assert.assertTrue; + +/** + * Provides a handle for the panel containing the person list. + */ +public class PersonListPanelHandle extends GuiHandle { + + public static final int NOT_FOUND = -1; + public static final String CARD_PANE_ID = "#cardPane"; + private static final String FILTER_FIELD_ID = "#filterField"; + private static final String PERSON_LIST_VIEW_ID = "#personListView"; + + public static final String DELETE_CONTEXT_MENU_ITEM_FIELD_ID = "#deleteMenuItem"; + + public PersonListPanelHandle(GuiRobot guiRobot, Stage primaryStage) { + super(guiRobot, primaryStage, TestApp.APP_TITLE); + } + + public boolean contains(String firstName, String lastName) { + //TODO: should be checking if the graphical node is displaying the names. + return getListView().getItems().stream().anyMatch(p -> p.hasName(firstName, lastName)); + } + + public boolean contains(int id) { + //TODO: should be checking if the graphical node is displaying the names. + return getListView().getItems().stream().anyMatch(p -> p.getId() == id); + } + + public boolean contains(Person person) { + return this.getPersonCardHandle(person) != null; + } + + public boolean isSelected(String firstName, String lastName) { + return getSelectedPersons().stream().filter(p -> p.hasName(firstName, lastName)).findAny().isPresent(); + } + + public boolean isOnlySelected(Person person) { + return isOnlySelected(person.getFirstName(), person.getLastName()); + } + + public boolean isOnlySelected(String firstName, String lastName) { + return getSelectedPersons().stream().filter(p -> p.hasName(firstName, lastName)).count() == 1; + } + + public boolean isSelected(Person person) { + return this.isSelected(person.getFirstName(), person.getLastName()); + } + + public List getSelectedPersons() { + ListView personList = getListView(); + return personList.getSelectionModel().getSelectedItems(); + } + + public ReadOnlyPerson getFirstSelectedPerson() { + return this.getSelectedPersons().get(0); + } + + public ListView getListView() { + return (ListView) getNode(PERSON_LIST_VIEW_ID); + } + + /** + * Clicks on the middle of the Listview. + * In order for headfull testing to work in travis ci, listview needs to be clicked before firing hot keys. + */ + public void clickOnListView() { + Point2D point= TestUtil.getScreenMidPoint(getListView()); + guiRobot.clickOn(point.getX(), point.getY()); + } + + /** + * Fires ContextMenuEvent which shows a contextmenu in the middle of the Listview. + */ + private void fireContextMenuEvent() { + Point2D screenPoint = TestUtil.getScreenMidPoint(getListView()); + Point2D scenePoint = TestUtil.getSceneMidPoint(getListView()); + Event event = new ContextMenuEvent(ContextMenuEvent.CONTEXT_MENU_REQUESTED, scenePoint.getX(), scenePoint.getY(), + screenPoint.getX(), screenPoint.getY(), false, + new PickResult(getListView(), screenPoint.getX(), screenPoint.getY())); + guiRobot.interact(() -> Event.fireEvent(getListView(), event)); + } + + public void use_LIST_JUMP_TO_INDEX_SHORTCUT(int index) { + switch (index) { + case 1: + guiRobot.push(TestUtil.scrub(new KeyCode[]{KeyCode.SHORTCUT, KeyCode.DIGIT1})); + break; + case 2: + guiRobot.push(TestUtil.scrub(new KeyCode[]{KeyCode.SHORTCUT, KeyCode.DIGIT2})); + break; + case 3: + guiRobot.push(TestUtil.scrub(new KeyCode[]{KeyCode.SHORTCUT, KeyCode.DIGIT3})); + break; + case 4: + guiRobot.push(TestUtil.scrub(new KeyCode[]{KeyCode.SHORTCUT, KeyCode.DIGIT4})); + break; + case 5: + guiRobot.push(TestUtil.scrub(new KeyCode[]{KeyCode.SHORTCUT, KeyCode.DIGIT5})); + break; + case 6: + guiRobot.push(TestUtil.scrub(new KeyCode[]{KeyCode.SHORTCUT, KeyCode.DIGIT6})); + break; + case 7: + guiRobot.push(TestUtil.scrub(new KeyCode[]{KeyCode.SHORTCUT, KeyCode.DIGIT7})); + break; + case 8: + guiRobot.push(TestUtil.scrub(new KeyCode[]{KeyCode.SHORTCUT, KeyCode.DIGIT8})); + break; + case 9: + guiRobot.push(TestUtil.scrub(new KeyCode[]{KeyCode.SHORTCUT, KeyCode.DIGIT9})); + break; + default: + throw new RuntimeException("Unsupported shortcut"); + } + } + + /** + * Navigate the listview to display and select the person. + * @param person + */ + public PersonCardHandle navigateToPerson(Person person) { + int index = getPersonIndex(person); + + guiRobot.interact(() -> { + getListView().scrollTo(index); + guiRobot.sleep(150); + getListView().getSelectionModel().select(index); + }); + guiRobot.sleep(100); + return getPersonCardHandle(person); + } + + public void navigateUp() { + guiRobot.push(KeyCode.UP); + } + + public void navigateDown() { + guiRobot.push(KeyCode.DOWN); + } + + /** + * Checks if the error dialog window for no selected person is shown + * @return + */ + public boolean isNoSelectedPersonDialogShown() { + try{ + Window window = guiRobot.window("Invalid Selection"); + return window != null && window.isShowing(); + } catch (NoSuchElementException e) { + return false; + } + } + + public void clickOnPerson(Person person) { + guiRobot.clickOn(person.getFirstName()); + } + + /** + * Right click on Person to show context menu. + * @param person + */ + public PersonListPanelHandle rightClickOnPerson(Person person) { + //Instead of using guiRobot.rightCickOn(), We will be firing off contextmenu request manually. + //As there is a bug in monocle that doesn't show contextmenu by actual right clicking. + //Refer to https://github.com/TestFX/Monocle/issues/12 + clickOnPerson(person); + fireContextMenuEvent(); + guiRobot.sleep(100); + assertTrue(isContextMenuShown()); + return this; + } + + private boolean isContextMenuShown() { + return isNodePresent(DELETE_CONTEXT_MENU_ITEM_FIELD_ID); + } + + private boolean isNodePresent(String fieldId) { + try { + getNode(fieldId); + return true; + } catch (IllegalStateException e) { + return false; + } + } + + + public void clickOnPerson(String personName) { + guiRobot.clickOn(personName); + } + + public void enterFilterAndApply(String filterText) { + typeTextField(FILTER_FIELD_ID, filterText); + pressEnter(); + } + + public String getFilterText() { + return getTextFieldText(FILTER_FIELD_ID); + } + + private void clickOnMultipleNames(List listOfNames) { + guiRobot.press(KeyCode.SHORTCUT); + listOfNames.forEach(guiRobot::clickOn); + guiRobot.release(KeyCode.SHORTCUT); + } + + /** + * Attempts to select multiple person cards + * + * Currently, this is done programmatically since multiple selection has problems in headless mode + * + * @param listOfPersons + */ + public void selectMultiplePersons(List listOfPersons) { + listOfPersons.stream().forEach(vPerson -> getListView().getSelectionModel().select(vPerson)); + } + + /** + * Returns true if the {@code persons} appear as a sub list (in that order) in the panel. + */ + public boolean containsInOrder(Person... persons) { + assert persons.length >= 2; + int indexOfFirstPerson = getPersonIndex(persons[0]); + if (indexOfFirstPerson == NOT_FOUND) return false; + return containsInOrder(indexOfFirstPerson, persons); + } + + public void clearSelection() { + getListView().getSelectionModel().clearSelection(); + } + + /** + * Returns true if the {@code persons} appear as the sub list (in that order) at position {@code startPosition}. + */ + public boolean containsInOrder(int startPosition, Person... persons) { + List personsInList = getListView().getItems(); + + // Return false if the list in panel is too short to contain the given list + if (startPosition + persons.length > personsInList.size()){ + return false; + } + + // Return false if any of the persons doesn't match + for (int i = 0; i < persons.length; i++) { + if (!personsInList.get(startPosition + i).fullName().equals(persons[i].fullName())){ + return false; + } + } + + return true; + } + + /** + * Returns the position of the person given, {@code NOT_FOUND} if not found in the list. + */ + public int getPersonIndex(Person targetPerson) { + List personsInList = getListView().getItems(); + for (int i = 0; i < personsInList.size(); i++) { + if(personsInList.get(i).getFirstName().equals(targetPerson.getFirstName())){ + return i; + } + } + return NOT_FOUND; + } + + public PersonCardHandle getPersonCardHandle(int index) { + return getPersonCardHandle(new Person(getListView().getItems().get(index))); + } + + /** + * Checks if the list is showing the person details correctly and in correct order. + * @param startPosition The starting position of the sub list. + * @param persons A list of person in the correct order. + * @return + */ + public boolean isListMatching(int startPosition, Person... persons) throws IllegalArgumentException { + if (persons.length + startPosition != getListView().getItems().size()) { + throw new IllegalArgumentException("List size not matching\n" + + "Expect " + (getListView().getItems().size()) + "persons"); + } + assertTrue(this.containsInOrder(startPosition, persons)); + for (int i = 0; i < persons.length; i++) { + final int scrollTo = i + startPosition; + guiRobot.interact(() -> getListView().scrollTo(scrollTo)); + guiRobot.sleep(200); + if (!TestUtil.compareCardAndPerson(getPersonCardHandle(startPosition + i), persons[i])) { + return false; + } + } + return true; + } + + public boolean containsListOnly(List personList) { + ReadOnlyPerson[] personArray = new Person[personList.size()]; + personList.toArray(personArray); + return containsListOnly(personArray); + } + + public boolean containsListOnly(ReadOnlyPerson... persons) { + if (persons.length != getListView().getItems().size()) return false; + + for (ReadOnlyPerson person : persons) { + if (!contains(person.getId())) return false; + } + + return true; + } + + /** + * Checks if the list is showing the person details correctly and in correct order. + * @param persons A list of person in the correct order. + * @return + */ + public boolean isListMatching(Person... persons) { + System.out.println("person length: " + persons.length); + return this.isListMatching(0, persons); + } + + public PersonCardHandle getPersonCardHandle(Person person) { + Set nodes = getAllCardNodes(); + Optional personCardNode = nodes.stream() + .filter(n -> new PersonCardHandle(guiRobot, primaryStage, n).isSamePerson(person)) + .findFirst(); + if (personCardNode.isPresent()) { + return new PersonCardHandle(guiRobot, primaryStage, personCardNode.get()); + } else { + return null; + } + } + + /** + * Select cards + * @param persons + * @return + */ + public List selectCards(Person... persons) { + guiRobot.press(KeyCode.SHORTCUT); + for (Person person: persons) { + guiRobot.interact(() -> { + getListView().scrollTo(getPersonIndex(person)); + guiRobot.sleep(150); + getListView().getSelectionModel().select(getPersonIndex(person)); + }); + } + guiRobot.release(KeyCode.SHORTCUT); + return getSelectedCards(); + } + + public PersonCardHandle selectCard(Person person) { + guiRobot.interact(() -> getListView().scrollTo(getPersonIndex(person))); + guiRobot.sleep(150); + clickOnPerson(person); + guiRobot.sleep(500); + return getPersonCardHandle(person); + } + + protected Set getAllCardNodes() { + return guiRobot.lookup(CARD_PANE_ID).queryAll(); + } + + public List getSelectedCards() { + ObservableList persons = getListView().getSelectionModel().getSelectedItems(); + return persons.stream().map(p -> getPersonCardHandle(new Person(p))) + .collect(Collectors.toCollection(ArrayList::new)); + } + + public int getSelectedCardSize() { + return getListView().getSelectionModel().getSelectedItems().size(); + } + +} diff --git a/src/test/java/guiunittests/GuiUnitTestBase.java b/src/test/java/guiunittests/GuiUnitTestBase.java new file mode 100644 index 000000000000..dd9b185e2f37 --- /dev/null +++ b/src/test/java/guiunittests/GuiUnitTestBase.java @@ -0,0 +1,56 @@ +package guiunittests; + +import guitests.GuiRobot; +import javafx.stage.Stage; +import org.junit.After; +import org.junit.Before; +import org.junit.BeforeClass; +import org.testfx.api.FxToolkit; +import org.testfx.framework.junit.ApplicationAdapter; +import org.testfx.framework.junit.ApplicationFixture; + +import java.util.concurrent.TimeoutException; + +/** + * A test base class for GUI unit tests classes that does the basic low level initialization for GUI testing. + */ +public abstract class GuiUnitTestBase implements ApplicationFixture { + + public GuiRobot guiRobot = new GuiRobot(); + + @BeforeClass + public static void beforeClass() { + try { + FxToolkit.registerPrimaryStage(); + FxToolkit.hideStage(); + } catch (TimeoutException e) { + e.printStackTrace(); + } + } + + @Before + public final void before() + throws Exception { + FxToolkit.registerPrimaryStage(); + FxToolkit.setupApplication(() -> new ApplicationAdapter(this)); + guiRobot.sleep(1000); + } + + @After + public final void after() + throws Exception { + FxToolkit.cleanupApplication(new ApplicationAdapter(this)); + } + + @Override + public void init() throws Exception { + + } + + @Override + public abstract void start(Stage stage) throws Exception; + + @Override + public void stop() throws Exception {} + +}