diff --git a/.github/workflows/arm.yml b/.github/workflows/arm.yml index f7cc44c..7f8d11a 100644 --- a/.github/workflows/arm.yml +++ b/.github/workflows/arm.yml @@ -28,7 +28,7 @@ jobs: export DEBIAN_FRONTEND=noninteractive ln -fs /usr/share/zoneinfo/America/New_York /etc/localtime apt update - apt install -y curl lsb-release sudo clang + apt install -y curl lsb-release sudo clang git RELEASE_DOT=$(lsb_release -sr) RELEASE_NUM=${RELEASE_DOT//[-._]/} RELEASE_NAME=$(lsb_release -sc) diff --git a/.gitignore b/.gitignore index f60c665..c4dfa02 100644 --- a/.gitignore +++ b/.gitignore @@ -167,3 +167,6 @@ Pods Brewfile.lock.json Package.resolved Documentation/Reference/README.md + +*.dump +*.dump.zip \ No newline at end of file diff --git a/.swiftformat b/.swiftformat index 02d7018..698077f 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,4 +1,5 @@ --indent 2 --header strip --commas inline +--disable wrapMultilineStatementBraces --exclude .build, DerivedData diff --git a/Configuration/etc/nginx/sites-available/orchardnest.conf b/Configuration/etc/nginx/sites-available/orchardnest.conf new file mode 100644 index 0000000..0dfb6a9 --- /dev/null +++ b/Configuration/etc/nginx/sites-available/orchardnest.conf @@ -0,0 +1,21 @@ +server { + server_name orchardnest.com; + listen 80; + + root /home/orchardnest/app/Public; + + location / { + try_files $uri @proxy; + } + + location @proxy { + proxy_pass http://127.0.0.1:8080; + proxy_pass_header Server; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass_header Server; + proxy_connect_timeout 3s; + proxy_read_timeout 10s; + } +} diff --git a/Configuration/etc/supervisor/conf.d/orchardnestd.conf b/Configuration/etc/supervisor/conf.d/orchardnestd.conf new file mode 100644 index 0000000..16dbb09 --- /dev/null +++ b/Configuration/etc/supervisor/conf.d/orchardnestd.conf @@ -0,0 +1,7 @@ +[program:orchardnestd] +command=/home/orchardnest/app/.build/release/orchardnestd serve --env production +directory=/home/orchardnest/app +user=orchardnest +environment=DATABASE_URL='postgres://orchardnest:12345@localhost/orchardnest' +stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log +stderr_logfile=/var/log/supervisor/%(program_name)-stderr.log diff --git a/Configuration/etc/supervisor/conf.d/orchardnestq.conf b/Configuration/etc/supervisor/conf.d/orchardnestq.conf new file mode 100644 index 0000000..98db60c --- /dev/null +++ b/Configuration/etc/supervisor/conf.d/orchardnestq.conf @@ -0,0 +1,7 @@ +[program:orchardnestq] +command=/home/orchardnest/app/.build/release/orchardnestd queues --scheduled --env production +directory=/home/orchardnest/app +user=orchardnest +environment=DATABASE_URL='postgres://orchardnest:12345@localhost/orchardnest' +stdout_logfile=/var/log/supervisor/%(program_name)-stdout.log +stderr_logfile=/var/log/supervisor/%(program_name)-stderr.log diff --git a/Documentation/Reference/classes/BlogReader.md b/Documentation/Reference/classes/BlogReader.md deleted file mode 100644 index caf0e5b..0000000 --- a/Documentation/Reference/classes/BlogReader.md +++ /dev/null @@ -1,20 +0,0 @@ -**CLASS** - -# `BlogReader` - -```swift -public class BlogReader -``` - -## Methods -### `init()` - -```swift -public init() -``` - -### `sites(fromURL:)` - -```swift -public func sites(fromURL url: URL) throws -> [LanguageContent] -``` diff --git a/Documentation/Reference/enums/EntryCategory.md b/Documentation/Reference/enums/EntryCategory.md new file mode 100644 index 0000000..ef6059f --- /dev/null +++ b/Documentation/Reference/enums/EntryCategory.md @@ -0,0 +1,106 @@ +**ENUM** + +# `EntryCategory` + +```swift +public enum EntryCategory: Codable +``` + +## Cases +### `companies` + +```swift +case companies +``` + +### `design` + +```swift +case design +``` + +### `development` + +```swift +case development +``` + +### `marketing` + +```swift +case marketing +``` + +### `newsletters` + +```swift +case newsletters +``` + +### `podcasts(_:)` + +```swift +case podcasts(URL) +``` + +### `updates` + +```swift +case updates +``` + +### `youtube(_:)` + +```swift +case youtube(String) +``` + +## Properties +### `type` + +```swift +public var type: EntryCategoryType +``` + +## Methods +### `init(podcastEpisodeAtURL:)` + +```swift +public init(podcastEpisodeAtURL url: URL) +``` + +### `init(youtubeVideoWithID:)` + +```swift +public init(youtubeVideoWithID id: String) +``` + +### `init(type:)` + +```swift +public init(type: EntryCategoryType) throws +``` + +### `init(from:)` + +```swift +public init(from decoder: Decoder) throws +``` + +#### Parameters + +| Name | Description | +| ---- | ----------- | +| decoder | The decoder to read data from. | + +### `encode(to:)` + +```swift +public func encode(to encoder: Encoder) throws +``` + +#### Parameters + +| Name | Description | +| ---- | ----------- | +| encoder | The encoder to write data to. | \ No newline at end of file diff --git a/Documentation/Reference/enums/EntryCategoryType.md b/Documentation/Reference/enums/EntryCategoryType.md new file mode 100644 index 0000000..aaabe24 --- /dev/null +++ b/Documentation/Reference/enums/EntryCategoryType.md @@ -0,0 +1,56 @@ +**ENUM** + +# `EntryCategoryType` + +```swift +public enum EntryCategoryType: String, Codable +``` + +## Cases +### `companies` + +```swift +case companies +``` + +### `design` + +```swift +case design +``` + +### `development` + +```swift +case development +``` + +### `marketing` + +```swift +case marketing +``` + +### `newsletters` + +```swift +case newsletters +``` + +### `podcasts` + +```swift +case podcasts +``` + +### `updates` + +```swift +case updates +``` + +### `youtube` + +```swift +case youtube +``` diff --git a/Documentation/Reference/extensions/EntryItem.md b/Documentation/Reference/extensions/EntryItem.md new file mode 100644 index 0000000..f7d74d2 --- /dev/null +++ b/Documentation/Reference/extensions/EntryItem.md @@ -0,0 +1,25 @@ +**EXTENSION** + +# `EntryItem` +```swift +public extension EntryItem +``` + +## Properties +### `podcastEpisodeURL` + +```swift +var podcastEpisodeURL: URL? +``` + +### `youtubeID` + +```swift +var youtubeID: String? +``` + +### `twitterShareLink` + +```swift +var twitterShareLink: String +``` diff --git a/Documentation/Reference/structs/EntryChannel.md b/Documentation/Reference/structs/EntryChannel.md new file mode 100644 index 0000000..6bd4a4a --- /dev/null +++ b/Documentation/Reference/structs/EntryChannel.md @@ -0,0 +1,65 @@ +**STRUCT** + +# `EntryChannel` + +```swift +public struct EntryChannel: Codable +``` + +## Properties +### `id` + +```swift +public let id: UUID +``` + +### `title` + +```swift +public let title: String +``` + +### `author` + +```swift +public let author: String +``` + +### `siteURL` + +```swift +public let siteURL: URL +``` + +### `twitterHandle` + +```swift +public let twitterHandle: String? +``` + +### `imageURL` + +```swift +public let imageURL: URL? +``` + +### `podcastAppleId` + +```swift +public let podcastAppleId: Int? +``` + +## Methods +### `init(id:title:siteURL:author:twitterHandle:imageURL:podcastAppleId:)` + +```swift +public init( + id: UUID, + title: String, + siteURL: URL, + author: String, + twitterHandle: String?, + imageURL: URL?, + podcastAppleId: Int? +) +``` diff --git a/Documentation/Reference/structs/EntryItem.md b/Documentation/Reference/structs/EntryItem.md new file mode 100644 index 0000000..faa5664 --- /dev/null +++ b/Documentation/Reference/structs/EntryItem.md @@ -0,0 +1,77 @@ +**STRUCT** + +# `EntryItem` + +```swift +public struct EntryItem: Codable +``` + +## Properties +### `id` + +```swift +public let id: UUID +``` + +### `channel` + +```swift +public let channel: EntryChannel +``` + +### `feedId` + +```swift +public let feedId: String +``` + +### `title` + +```swift +public let title: String +``` + +### `summary` + +```swift +public let summary: String +``` + +### `url` + +```swift +public let url: URL +``` + +### `imageURL` + +```swift +public let imageURL: URL? +``` + +### `publishedAt` + +```swift +public let publishedAt: Date +``` + +### `category` + +```swift +public let category: EntryCategory +``` + +## Methods +### `init(id:channel:category:feedId:title:summary:url:imageURL:publishedAt:)` + +```swift +public init(id: UUID, + channel: EntryChannel, + category: EntryCategory, + feedId: String, + title: String, + summary: String, + url: URL, + imageURL: URL?, + publishedAt: Date) +``` diff --git a/Documentation/Reference/structs/Channel.md b/Documentation/Reference/structs/FeedChannel.md similarity index 81% rename from Documentation/Reference/structs/Channel.md rename to Documentation/Reference/structs/FeedChannel.md index f9378c5..38ef056 100644 --- a/Documentation/Reference/structs/Channel.md +++ b/Documentation/Reference/structs/FeedChannel.md @@ -1,9 +1,9 @@ **STRUCT** -# `Channel` +# `FeedChannel` ```swift -public struct Channel: Codable +public struct FeedChannel: Codable ``` ## Properties @@ -76,7 +76,7 @@ public let category: String ### `items` ```swift -public let items: [Item] +public let items: [FeedItem] ``` ### `itemCount` @@ -92,8 +92,8 @@ public let itemCount: Int? public static func imageURL(fromYoutubeId ytId: String) -> URL ``` -### `init(language:category:site:)` +### `init(language:category:site:data:)` ```swift -public init(language: String, category: String, site: Site) throws +public init(language: String, category: String, site: Site, data: Data) throws ``` diff --git a/Documentation/Reference/structs/Item.md b/Documentation/Reference/structs/FeedItem.md similarity index 92% rename from Documentation/Reference/structs/Item.md rename to Documentation/Reference/structs/FeedItem.md index 32da125..16e6849 100644 --- a/Documentation/Reference/structs/Item.md +++ b/Documentation/Reference/structs/FeedItem.md @@ -1,9 +1,9 @@ **STRUCT** -# `Item` +# `FeedItem` ```swift -public struct Item: Codable +public struct FeedItem: Codable ``` ## Properties diff --git a/Package.swift b/Package.swift index 08f238f..ab623da 100644 --- a/Package.swift +++ b/Package.swift @@ -16,18 +16,19 @@ let package = Package( name: "OrchardNestServer", targets: ["OrchardNestServer"] ), - .executable(name: "orcnst-serve", targets: ["orcnst-serve"]), - .executable(name: "orcnst", targets: ["orcnst"]) + .executable(name: "orchardnestd", targets: ["orchardnestd"]) ], dependencies: [ // Dependencies declare other packages that this package depends on. .package(url: "https://github.com/brightdigit/FeedKit.git", .branch("master")), .package(url: "https://github.com/shibapm/Komondor", from: "1.0.5"), - .package(url: "https://github.com/eneko/SourceDocs", from: "1.0.0"), + .package(url: "https://github.com/eneko/SourceDocs", from: "1.2.1"), .package(url: "https://github.com/vapor/vapor.git", from: "4.0.0"), .package(url: "https://github.com/vapor/fluent.git", from: "4.0.0"), .package(url: "https://github.com/vapor/fluent-postgres-driver.git", from: "2.0.0"), - .package(name: "QueuesFluentDriver", url: "https://github.com/m-barthelemy/vapor-queues-fluent-driver.git", from: "0.3.8") + .package(name: "QueuesFluentDriver", url: "https://github.com/m-barthelemy/vapor-queues-fluent-driver.git", from: "0.3.8"), + .package(name: "Plot", url: "https://github.com/johnsundell/plot.git", from: "0.8.0"), + .package(url: "https://github.com/JohnSundell/Ink.git", from: "0.1.0") ], targets: [ // Targets are the basic building blocks of a package. A target can define a module or a test suite. @@ -42,12 +43,12 @@ let package = Package( .product(name: "Fluent", package: "fluent"), .product(name: "FluentPostgresDriver", package: "fluent-postgres-driver"), .product(name: "Vapor", package: "vapor"), - .product(name: "QueuesFluentDriver", package: "QueuesFluentDriver")] + .product(name: "QueuesFluentDriver", package: "QueuesFluentDriver"), + .product(name: "Plot", package: "Plot"), + .product(name: "Ink", package: "Ink")] ), - .target(name: "orcnst", - dependencies: ["OrchardNestKit", "FeedKit"]), - .target(name: "orcnst-serve", - dependencies: ["OrchardNestKit", "OrchardNestServer"]), + .target(name: "orchardnestd", + dependencies: ["OrchardNestKit", "OrchardNestServer", "FeedKit"]), .testTarget( name: "OrchardNestKitTests", dependencies: ["OrchardNestKit"] @@ -65,7 +66,7 @@ let package = Package( "swift test --enable-code-coverage --enable-test-discovery --generate-linuxmain", "swift run swiftformat .", "swift run swiftlint autocorrect", - "swift run sourcedocs generate --spm-module OrchardNest -r", + "swift run sourcedocs generate --spm-module OrchardNestKit -c -r", // "swift run swiftpmls mine", "git add .", "swift run swiftformat --lint .", diff --git a/Public/android-chrome-192x192.png b/Public/android-chrome-192x192.png new file mode 100644 index 0000000..b08deb9 Binary files /dev/null and b/Public/android-chrome-192x192.png differ diff --git a/Public/android-chrome-512x512.png b/Public/android-chrome-512x512.png new file mode 100644 index 0000000..12e6a48 Binary files /dev/null and b/Public/android-chrome-512x512.png differ diff --git a/Public/apple-touch-icon.png b/Public/apple-touch-icon.png new file mode 100644 index 0000000..a1cba81 Binary files /dev/null and b/Public/apple-touch-icon.png differ diff --git a/Public/browserconfig.xml b/Public/browserconfig.xml new file mode 100644 index 0000000..f9c2e67 --- /dev/null +++ b/Public/browserconfig.xml @@ -0,0 +1,9 @@ + + + + + + #2b5797 + + + diff --git a/Public/favicon-16x16.png b/Public/favicon-16x16.png new file mode 100644 index 0000000..fc45fd3 Binary files /dev/null and b/Public/favicon-16x16.png differ diff --git a/Public/favicon-32x32.png b/Public/favicon-32x32.png new file mode 100644 index 0000000..fc3facb Binary files /dev/null and b/Public/favicon-32x32.png differ diff --git a/Public/favicon.ico b/Public/favicon.ico new file mode 100644 index 0000000..4ed150a Binary files /dev/null and b/Public/favicon.ico differ diff --git a/Public/images/logo.png b/Public/images/logo.png new file mode 100644 index 0000000..f074929 Binary files /dev/null and b/Public/images/logo.png differ diff --git a/Public/images/logo.svg b/Public/images/logo.svg new file mode 100644 index 0000000..6b103df --- /dev/null +++ b/Public/images/logo.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Public/images/podcast-players/apple/badge.svg b/Public/images/podcast-players/apple/badge.svg new file mode 100755 index 0000000..6989219 --- /dev/null +++ b/Public/images/podcast-players/apple/badge.svg @@ -0,0 +1,160 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Public/images/podcast-players/apple/icon.png b/Public/images/podcast-players/apple/icon.png new file mode 100644 index 0000000..39eba4d Binary files /dev/null and b/Public/images/podcast-players/apple/icon.png differ diff --git a/Public/images/podcast-players/apple/icon.svg b/Public/images/podcast-players/apple/icon.svg new file mode 100644 index 0000000..e7248b7 --- /dev/null +++ b/Public/images/podcast-players/apple/icon.svg @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Public/images/podcast-players/castro/badge.svg b/Public/images/podcast-players/castro/badge.svg new file mode 100644 index 0000000..8ab53cd --- /dev/null +++ b/Public/images/podcast-players/castro/badge.svg @@ -0,0 +1,47 @@ + + + + Open in Castro + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Public/images/podcast-players/castro/icon.svg b/Public/images/podcast-players/castro/icon.svg new file mode 100644 index 0000000..6176ed1 --- /dev/null +++ b/Public/images/podcast-players/castro/icon.svg @@ -0,0 +1,23 @@ + + + + c2icon copy + Created with Sketch. + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Public/images/podcast-players/overcast/icon.png b/Public/images/podcast-players/overcast/icon.png new file mode 100644 index 0000000..ed489d9 Binary files /dev/null and b/Public/images/podcast-players/overcast/icon.png differ diff --git a/Public/images/podcast-players/overcast/icon.svg b/Public/images/podcast-players/overcast/icon.svg new file mode 100644 index 0000000..ae01e12 --- /dev/null +++ b/Public/images/podcast-players/overcast/icon.svg @@ -0,0 +1,7 @@ + + + + + + + diff --git a/Public/images/podcast-players/pocketcasts/badge.svg b/Public/images/podcast-players/pocketcasts/badge.svg new file mode 100644 index 0000000..5683099 --- /dev/null +++ b/Public/images/podcast-players/pocketcasts/badge.svg @@ -0,0 +1,40 @@ + + + + Badges/pocketcasts_medium_light + Created with Sketch. + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Public/images/podcast-players/pocketcasts/icon.svg b/Public/images/podcast-players/pocketcasts/icon.svg new file mode 100644 index 0000000..4413310 --- /dev/null +++ b/Public/images/podcast-players/pocketcasts/icon.svg @@ -0,0 +1,75 @@ + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + diff --git a/Public/mstile-150x150.png b/Public/mstile-150x150.png new file mode 100644 index 0000000..2802db1 Binary files /dev/null and b/Public/mstile-150x150.png differ diff --git a/Public/safari-pinned-tab.svg b/Public/safari-pinned-tab.svg new file mode 100644 index 0000000..ceb5027 --- /dev/null +++ b/Public/safari-pinned-tab.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/Public/site.webmanifest b/Public/site.webmanifest new file mode 100644 index 0000000..b20abb7 --- /dev/null +++ b/Public/site.webmanifest @@ -0,0 +1,19 @@ +{ + "name": "", + "short_name": "", + "icons": [ + { + "src": "/android-chrome-192x192.png", + "sizes": "192x192", + "type": "image/png" + }, + { + "src": "/android-chrome-512x512.png", + "sizes": "512x512", + "type": "image/png" + } + ], + "theme_color": "#ffffff", + "background_color": "#ffffff", + "display": "standalone" +} diff --git a/Public/styles/elusive-icons/css/elusive-icons.css b/Public/styles/elusive-icons/css/elusive-icons.css new file mode 100644 index 0000000..b6db7c2 --- /dev/null +++ b/Public/styles/elusive-icons/css/elusive-icons.css @@ -0,0 +1,1082 @@ +/*! + * Elusive Icons 2.0.0 by @ReduxFramework - http://elusiveicons.com - @reduxframework + * License - http://elusiveicons.com/license (Font: SIL OFL 1.1, CSS: MIT License) + */ +/* FONT PATH + * -------------------------- */ +@font-face { + font-family: 'Elusive-Icons'; + src: url('../fonts/elusiveicons-webfont.eot?v=2.0.0'); + src: url('../fonts/elusiveicons-webfont.eot?#iefix&v=2.0.0') format('embedded-opentype'), url('../fonts/elusiveicons-webfont.woff?v=2.0.0') format('woff'), url('../fonts/elusiveicons-webfont.ttf?v=2.0.0') format('truetype'), url('../fonts/elusiveicons-webfont.svg?v=2.0.0#elusiveiconsregular') format('svg'); + font-weight: normal; + font-style: normal; +} +.el { + display: inline-block; + font: normal normal normal 14px/1 'Elusive-Icons'; + font-size: inherit; + text-rendering: auto; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + transform: translate(0, 0); +} +/* makes the font 33% larger relative to the icon container */ +.el-lg { + font-size: 1.33333333em; + line-height: 0.75em; + vertical-align: -15%; +} +.el-2x { + font-size: 2em; +} +.el-3x { + font-size: 3em; +} +.el-4x { + font-size: 4em; +} +.el-5x { + font-size: 5em; +} +.el-fw { + width: 1.28571429em; + text-align: center; +} +.el-ul { + padding-left: 0; + margin-left: 2.14285714em; + list-style-type: none; +} +.el-ul > li { + position: relative; +} +.el-li { + position: absolute; + left: -2.14285714em; + width: 2.14285714em; + top: 0.14285714em; + text-align: center; +} +.el-li.el-lg { + left: -1.85714286em; +} +.el-border { + padding: .2em .25em .15em; + border: solid 0.08em #eeeeee; + border-radius: .1em; +} +.pull-right { + float: right; +} +.pull-left { + float: left; +} +.el.pull-left { + margin-right: .3em; +} +.el.pull-right { + margin-left: .3em; +} +.el-spin { + -webkit-animation: el-spin 2s infinite linear; + animation: el-spin 2s infinite linear; +} +.el-pulse { + -webkit-animation: el-spin 1s infinite steps(8); + animation: el-spin 1s infinite steps(8); +} +@-webkit-keyframes el-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +@keyframes el-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} +.el-rotate-90 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=1); + -webkit-transform: rotate(90deg); + -ms-transform: rotate(90deg); + transform: rotate(90deg); +} +.el-rotate-180 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2); + -webkit-transform: rotate(180deg); + -ms-transform: rotate(180deg); + transform: rotate(180deg); +} +.el-rotate-270 { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=3); + -webkit-transform: rotate(270deg); + -ms-transform: rotate(270deg); + transform: rotate(270deg); +} +.el-flip-horizontal { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1); + -webkit-transform: scale(-1, 1); + -ms-transform: scale(-1, 1); + transform: scale(-1, 1); +} +.el-flip-vertical { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1); + -webkit-transform: scale(1, -1); + -ms-transform: scale(1, -1); + transform: scale(1, -1); +} +:root .el-rotate-90, +:root .el-rotate-180, +:root .el-rotate-270, +:root .el-flip-horizontal, +:root .el-flip-vertical { + filter: none; +} +.el-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.el-stack-1x, +.el-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.el-stack-1x { + line-height: inherit; +} +.el-stack-2x { + font-size: 2em; +} +.el-inverse { + color: #ffffff; +} +/* Elusive Icons uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ +.el-address-book-alt:before { + content: "\f101"; +} +.el-address-book:before { + content: "\f102"; +} +.el-adjust-alt:before { + content: "\f103"; +} +.el-adjust:before { + content: "\f104"; +} +.el-adult:before { + content: "\f105"; +} +.el-align-center:before { + content: "\f106"; +} +.el-align-justify:before { + content: "\f107"; +} +.el-align-left:before { + content: "\f108"; +} +.el-align-right:before { + content: "\f109"; +} +.el-arrow-down:before { + content: "\f10a"; +} +.el-arrow-left:before { + content: "\f10b"; +} +.el-arrow-right:before { + content: "\f10c"; +} +.el-arrow-up:before { + content: "\f10d"; +} +.el-asl:before { + content: "\f10e"; +} +.el-asterisk:before { + content: "\f10f"; +} +.el-backward:before { + content: "\f110"; +} +.el-ban-circle:before { + content: "\f111"; +} +.el-barcode:before { + content: "\f112"; +} +.el-behance:before { + content: "\f113"; +} +.el-bell:before { + content: "\f114"; +} +.el-blind:before { + content: "\f115"; +} +.el-blogger:before { + content: "\f116"; +} +.el-bold:before { + content: "\f117"; +} +.el-book:before { + content: "\f118"; +} +.el-bookmark-empty:before { + content: "\f119"; +} +.el-bookmark:before { + content: "\f11a"; +} +.el-braille:before { + content: "\f11b"; +} +.el-briefcase:before { + content: "\f11c"; +} +.el-broom:before { + content: "\f11d"; +} +.el-brush:before { + content: "\f11e"; +} +.el-bulb:before { + content: "\f11f"; +} +.el-bullhorn:before { + content: "\f120"; +} +.el-calendar-sign:before { + content: "\f121"; +} +.el-calendar:before { + content: "\f122"; +} +.el-camera:before { + content: "\f123"; +} +.el-car:before { + content: "\f124"; +} +.el-caret-down:before { + content: "\f125"; +} +.el-caret-left:before { + content: "\f126"; +} +.el-caret-right:before { + content: "\f127"; +} +.el-caret-up:before { + content: "\f128"; +} +.el-cc:before { + content: "\f129"; +} +.el-certificate:before { + content: "\f12a"; +} +.el-check-empty:before { + content: "\f12b"; +} +.el-check:before { + content: "\f12c"; +} +.el-chevron-down:before { + content: "\f12d"; +} +.el-chevron-left:before { + content: "\f12e"; +} +.el-chevron-right:before { + content: "\f12f"; +} +.el-chevron-up:before { + content: "\f130"; +} +.el-child:before { + content: "\f131"; +} +.el-circle-arrow-down:before { + content: "\f132"; +} +.el-circle-arrow-left:before { + content: "\f133"; +} +.el-circle-arrow-right:before { + content: "\f134"; +} +.el-circle-arrow-up:before { + content: "\f135"; +} +.el-cloud-alt:before { + content: "\f136"; +} +.el-cloud:before { + content: "\f137"; +} +.el-cog-alt:before { + content: "\f138"; +} +.el-cog:before { + content: "\f139"; +} +.el-cogs:before { + content: "\f13a"; +} +.el-comment-alt:before { + content: "\f13b"; +} +.el-comment:before { + content: "\f13c"; +} +.el-compass-alt:before { + content: "\f13d"; +} +.el-compass:before { + content: "\f13e"; +} +.el-credit-card:before { + content: "\f13f"; +} +.el-css:before { + content: "\f140"; +} +.el-dashboard:before { + content: "\f141"; +} +.el-delicious:before { + content: "\f142"; +} +.el-deviantart:before { + content: "\f143"; +} +.el-digg:before { + content: "\f144"; +} +.el-download-alt:before { + content: "\f145"; +} +.el-download:before { + content: "\f146"; +} +.el-dribbble:before { + content: "\f147"; +} +.el-edit:before { + content: "\f148"; +} +.el-eject:before { + content: "\f149"; +} +.el-envelope-alt:before { + content: "\f14a"; +} +.el-envelope:before { + content: "\f14b"; +} +.el-error-alt:before { + content: "\f14c"; +} +.el-error:before { + content: "\f14d"; +} +.el-eur:before { + content: "\f14e"; +} +.el-exclamation-sign:before { + content: "\f14f"; +} +.el-eye-close:before { + content: "\f150"; +} +.el-eye-open:before { + content: "\f151"; +} +.el-facebook:before { + content: "\f152"; +} +.el-facetime-video:before { + content: "\f153"; +} +.el-fast-backward:before { + content: "\f154"; +} +.el-fast-forward:before { + content: "\f155"; +} +.el-female:before { + content: "\f156"; +} +.el-file-alt:before { + content: "\f157"; +} +.el-file-edit-alt:before { + content: "\f158"; +} +.el-file-edit:before { + content: "\f159"; +} +.el-file-new-alt:before { + content: "\f15a"; +} +.el-file-new:before { + content: "\f15b"; +} +.el-file:before { + content: "\f15c"; +} +.el-film:before { + content: "\f15d"; +} +.el-filter:before { + content: "\f15e"; +} +.el-fire:before { + content: "\f15f"; +} +.el-flag-alt:before { + content: "\f160"; +} +.el-flag:before { + content: "\f161"; +} +.el-flickr:before { + content: "\f162"; +} +.el-folder-close:before { + content: "\f163"; +} +.el-folder-open:before { + content: "\f164"; +} +.el-folder-sign:before { + content: "\f165"; +} +.el-folder:before { + content: "\f166"; +} +.el-font:before { + content: "\f167"; +} +.el-fontsize:before { + content: "\f168"; +} +.el-fork:before { + content: "\f169"; +} +.el-forward-alt:before { + content: "\f16a"; +} +.el-forward:before { + content: "\f16b"; +} +.el-foursquare:before { + content: "\f16c"; +} +.el-friendfeed-rect:before { + content: "\f16d"; +} +.el-friendfeed:before { + content: "\f16e"; +} +.el-fullscreen:before { + content: "\f16f"; +} +.el-gbp:before { + content: "\f170"; +} +.el-gift:before { + content: "\f171"; +} +.el-github-text:before { + content: "\f172"; +} +.el-github:before { + content: "\f173"; +} +.el-glass:before { + content: "\f174"; +} +.el-glasses:before { + content: "\f175"; +} +.el-globe-alt:before { + content: "\f176"; +} +.el-globe:before { + content: "\f177"; +} +.el-googleplus:before { + content: "\f178"; +} +.el-graph-alt:before { + content: "\f179"; +} +.el-graph:before { + content: "\f17a"; +} +.el-group-alt:before { + content: "\f17b"; +} +.el-group:before { + content: "\f17c"; +} +.el-guidedog:before { + content: "\f17d"; +} +.el-hand-down:before { + content: "\f17e"; +} +.el-hand-left:before { + content: "\f17f"; +} +.el-hand-right:before { + content: "\f180"; +} +.el-hand-up:before { + content: "\f181"; +} +.el-hdd:before { + content: "\f182"; +} +.el-headphones:before { + content: "\f183"; +} +.el-hearing-impaired:before { + content: "\f184"; +} +.el-heart-alt:before { + content: "\f185"; +} +.el-heart-empty:before { + content: "\f186"; +} +.el-heart:before { + content: "\f187"; +} +.el-home-alt:before { + content: "\f188"; +} +.el-home:before { + content: "\f189"; +} +.el-hourglass:before { + content: "\f18a"; +} +.el-idea-alt:before { + content: "\f18b"; +} +.el-idea:before { + content: "\f18c"; +} +.el-inbox-alt:before { + content: "\f18d"; +} +.el-inbox-box:before { + content: "\f18e"; +} +.el-inbox:before { + content: "\f18f"; +} +.el-indent-left:before { + content: "\f190"; +} +.el-indent-right:before { + content: "\f191"; +} +.el-info-circle:before { + content: "\f192"; +} +.el-instagram:before { + content: "\f193"; +} +.el-iphone-home:before { + content: "\f194"; +} +.el-italic:before { + content: "\f195"; +} +.el-key:before { + content: "\f196"; +} +.el-laptop-alt:before { + content: "\f197"; +} +.el-laptop:before { + content: "\f198"; +} +.el-lastfm:before { + content: "\f199"; +} +.el-leaf:before { + content: "\f19a"; +} +.el-lines:before { + content: "\f19b"; +} +.el-link:before { + content: "\f19c"; +} +.el-linkedin:before { + content: "\f19d"; +} +.el-list-alt:before { + content: "\f19e"; +} +.el-list:before { + content: "\f19f"; +} +.el-livejournal:before { + content: "\f1a0"; +} +.el-lock-alt:before { + content: "\f1a1"; +} +.el-lock:before { + content: "\f1a2"; +} +.el-magic:before { + content: "\f1a3"; +} +.el-magnet:before { + content: "\f1a4"; +} +.el-male:before { + content: "\f1a5"; +} +.el-map-marker-alt:before { + content: "\f1a6"; +} +.el-map-marker:before { + content: "\f1a7"; +} +.el-mic-alt:before { + content: "\f1a8"; +} +.el-mic:before { + content: "\f1a9"; +} +.el-minus-sign:before { + content: "\f1aa"; +} +.el-minus:before { + content: "\f1ab"; +} +.el-move:before { + content: "\f1ac"; +} +.el-music:before { + content: "\f1ad"; +} +.el-myspace:before { + content: "\f1ae"; +} +.el-network:before { + content: "\f1af"; +} +.el-off:before { + content: "\f1b0"; +} +.el-ok-circle:before { + content: "\f1b1"; +} +.el-ok-sign:before { + content: "\f1b2"; +} +.el-ok:before { + content: "\f1b3"; +} +.el-opensource:before { + content: "\f1b4"; +} +.el-paper-clip-alt:before { + content: "\f1b5"; +} +.el-paper-clip:before { + content: "\f1b6"; +} +.el-path:before { + content: "\f1b7"; +} +.el-pause-alt:before { + content: "\f1b8"; +} +.el-pause:before { + content: "\f1b9"; +} +.el-pencil-alt:before { + content: "\f1ba"; +} +.el-pencil:before { + content: "\f1bb"; +} +.el-person:before { + content: "\f1bc"; +} +.el-phone-alt:before { + content: "\f1bd"; +} +.el-phone:before { + content: "\f1be"; +} +.el-photo-alt:before { + content: "\f1bf"; +} +.el-photo:before { + content: "\f1c0"; +} +.el-picasa:before { + content: "\f1c1"; +} +.el-picture:before { + content: "\f1c2"; +} +.el-pinterest:before { + content: "\f1c3"; +} +.el-plane:before { + content: "\f1c4"; +} +.el-play-alt:before { + content: "\f1c5"; +} +.el-play-circle:before { + content: "\f1c6"; +} +.el-play:before { + content: "\f1c7"; +} +.el-plurk-alt:before { + content: "\f1c8"; +} +.el-plurk:before { + content: "\f1c9"; +} +.el-plus-sign:before { + content: "\f1ca"; +} +.el-plus:before { + content: "\f1cb"; +} +.el-podcast:before { + content: "\f1cc"; +} +.el-print:before { + content: "\f1cd"; +} +.el-puzzle:before { + content: "\f1ce"; +} +.el-qrcode:before { + content: "\f1cf"; +} +.el-question-sign:before { + content: "\f1d0"; +} +.el-question:before { + content: "\f1d1"; +} +.el-quote-alt:before { + content: "\f1d2"; +} +.el-quote-right-alt:before { + content: "\f1d3"; +} +.el-quote-right:before { + content: "\f1d4"; +} +.el-quotes:before { + content: "\f1d5"; +} +.el-random:before { + content: "\f1d6"; +} +.el-record:before { + content: "\f1d7"; +} +.el-reddit:before { + content: "\f1d8"; +} +.el-redux:before { + content: "\f1d9"; +} +.el-refresh:before { + content: "\f1da"; +} +.el-remove-circle:before { + content: "\f1db"; +} +.el-remove-sign:before { + content: "\f1dc"; +} +.el-remove:before { + content: "\f1dd"; +} +.el-repeat-alt:before { + content: "\f1de"; +} +.el-repeat:before { + content: "\f1df"; +} +.el-resize-full:before { + content: "\f1e0"; +} +.el-resize-horizontal:before { + content: "\f1e1"; +} +.el-resize-small:before { + content: "\f1e2"; +} +.el-resize-vertical:before { + content: "\f1e3"; +} +.el-return-key:before { + content: "\f1e4"; +} +.el-retweet:before { + content: "\f1e5"; +} +.el-reverse-alt:before { + content: "\f1e6"; +} +.el-road:before { + content: "\f1e7"; +} +.el-rss:before { + content: "\f1e8"; +} +.el-scissors:before { + content: "\f1e9"; +} +.el-screen-alt:before { + content: "\f1ea"; +} +.el-screen:before { + content: "\f1eb"; +} +.el-screenshot:before { + content: "\f1ec"; +} +.el-search-alt:before { + content: "\f1ed"; +} +.el-search:before { + content: "\f1ee"; +} +.el-share-alt:before { + content: "\f1ef"; +} +.el-share:before { + content: "\f1f0"; +} +.el-shopping-cart-sign:before { + content: "\f1f1"; +} +.el-shopping-cart:before { + content: "\f1f2"; +} +.el-signal:before { + content: "\f1f3"; +} +.el-skype:before { + content: "\f1f4"; +} +.el-slideshare:before { + content: "\f1f5"; +} +.el-smiley-alt:before { + content: "\f1f6"; +} +.el-smiley:before { + content: "\f1f7"; +} +.el-soundcloud:before { + content: "\f1f8"; +} +.el-speaker:before { + content: "\f1f9"; +} +.el-spotify:before { + content: "\f1fa"; +} +.el-stackoverflow:before { + content: "\f1fb"; +} +.el-star-alt:before { + content: "\f1fc"; +} +.el-star-empty:before { + content: "\f1fd"; +} +.el-star:before { + content: "\f1fe"; +} +.el-step-backward:before { + content: "\f1ff"; +} +.el-step-forward:before { + content: "\f200"; +} +.el-stop-alt:before { + content: "\f201"; +} +.el-stop:before { + content: "\f202"; +} +.el-stumbleupon:before { + content: "\f203"; +} +.el-tag:before { + content: "\f204"; +} +.el-tags:before { + content: "\f205"; +} +.el-tasks:before { + content: "\f206"; +} +.el-text-height:before { + content: "\f207"; +} +.el-text-width:before { + content: "\f208"; +} +.el-th-large:before { + content: "\f209"; +} +.el-th-list:before { + content: "\f20a"; +} +.el-th:before { + content: "\f20b"; +} +.el-thumbs-down:before { + content: "\f20c"; +} +.el-thumbs-up:before { + content: "\f20d"; +} +.el-time-alt:before { + content: "\f20e"; +} +.el-time:before { + content: "\f20f"; +} +.el-tint:before { + content: "\f210"; +} +.el-torso:before { + content: "\f211"; +} +.el-trash-alt:before { + content: "\f212"; +} +.el-trash:before { + content: "\f213"; +} +.el-tumblr:before { + content: "\f214"; +} +.el-twitter:before { + content: "\f215"; +} +.el-universal-access:before { + content: "\f216"; +} +.el-unlock-alt:before { + content: "\f217"; +} +.el-unlock:before { + content: "\f218"; +} +.el-upload:before { + content: "\f219"; +} +.el-usd:before { + content: "\f21a"; +} +.el-user:before { + content: "\f21b"; +} +.el-viadeo:before { + content: "\f21c"; +} +.el-video-alt:before { + content: "\f21d"; +} +.el-video-chat:before { + content: "\f21e"; +} +.el-video:before { + content: "\f21f"; +} +.el-view-mode:before { + content: "\f220"; +} +.el-vimeo:before { + content: "\f221"; +} +.el-vkontakte:before { + content: "\f222"; +} +.el-volume-down:before { + content: "\f223"; +} +.el-volume-off:before { + content: "\f224"; +} +.el-volume-up:before { + content: "\f225"; +} +.el-w3c:before { + content: "\f226"; +} +.el-warning-sign:before { + content: "\f227"; +} +.el-website-alt:before { + content: "\f228"; +} +.el-website:before { + content: "\f229"; +} +.el-wheelchair:before { + content: "\f22a"; +} +.el-wordpress:before { + content: "\f22b"; +} +.el-wrench-alt:before { + content: "\f22c"; +} +.el-wrench:before { + content: "\f22d"; +} +.el-youtube:before { + content: "\f22e"; +} +.el-zoom-in:before { + content: "\f22f"; +} +.el-zoom-out:before { + content: "\f230"; +} diff --git a/Public/styles/elusive-icons/css/elusive-icons.min.css b/Public/styles/elusive-icons/css/elusive-icons.min.css new file mode 100644 index 0000000..f053926 --- /dev/null +++ b/Public/styles/elusive-icons/css/elusive-icons.min.css @@ -0,0 +1,4 @@ +/*! + * Elusive Icons 2.0.0 by @ReduxFramework - http://elusiveicons.com - @reduxframework + * License - http://elusiveicons.com/license (Font: SIL OFL 1.1, CSS: MIT License) + */@font-face{font-family:'Elusive-Icons';src:url('../fonts/elusiveicons-webfont.eot?v=2.0.0');src:url('../fonts/elusiveicons-webfont.eot?#iefix&v=2.0.0') format('embedded-opentype'),url('../fonts/elusiveicons-webfont.woff?v=2.0.0') format('woff'),url('../fonts/elusiveicons-webfont.ttf?v=2.0.0') format('truetype'),url('../fonts/elusiveicons-webfont.svg?v=2.0.0#elusiveiconsregular') format('svg');font-weight:normal;font-style:normal}.el{display:inline-block;font:normal normal normal 14px/1 'Elusive-Icons';font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale;transform:translate(0, 0)}.el-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.el-2x{font-size:2em}.el-3x{font-size:3em}.el-4x{font-size:4em}.el-5x{font-size:5em}.el-fw{width:1.28571429em;text-align:center}.el-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.el-ul>li{position:relative}.el-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.el-li.el-lg{left:-1.85714286em}.el-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.pull-right{float:right}.pull-left{float:left}.el.pull-left{margin-right:.3em}.el.pull-right{margin-left:.3em}.el-spin{-webkit-animation:el-spin 2s infinite linear;animation:el-spin 2s infinite linear}.el-pulse{-webkit-animation:el-spin 1s infinite steps(8);animation:el-spin 1s infinite steps(8)}@-webkit-keyframes el-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes el-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.el-rotate-90{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=1);-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.el-rotate-180{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2);-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.el-rotate-270{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=3);-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.el-flip-horizontal{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1);-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.el-flip-vertical{filter:progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1);-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .el-rotate-90,:root .el-rotate-180,:root .el-rotate-270,:root .el-flip-horizontal,:root .el-flip-vertical{filter:none}.el-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.el-stack-1x,.el-stack-2x{position:absolute;left:0;width:100%;text-align:center}.el-stack-1x{line-height:inherit}.el-stack-2x{font-size:2em}.el-inverse{color:#fff}.el-address-book-alt:before{content:"\f101"}.el-address-book:before{content:"\f102"}.el-adjust-alt:before{content:"\f103"}.el-adjust:before{content:"\f104"}.el-adult:before{content:"\f105"}.el-align-center:before{content:"\f106"}.el-align-justify:before{content:"\f107"}.el-align-left:before{content:"\f108"}.el-align-right:before{content:"\f109"}.el-arrow-down:before{content:"\f10a"}.el-arrow-left:before{content:"\f10b"}.el-arrow-right:before{content:"\f10c"}.el-arrow-up:before{content:"\f10d"}.el-asl:before{content:"\f10e"}.el-asterisk:before{content:"\f10f"}.el-backward:before{content:"\f110"}.el-ban-circle:before{content:"\f111"}.el-barcode:before{content:"\f112"}.el-behance:before{content:"\f113"}.el-bell:before{content:"\f114"}.el-blind:before{content:"\f115"}.el-blogger:before{content:"\f116"}.el-bold:before{content:"\f117"}.el-book:before{content:"\f118"}.el-bookmark-empty:before{content:"\f119"}.el-bookmark:before{content:"\f11a"}.el-braille:before{content:"\f11b"}.el-briefcase:before{content:"\f11c"}.el-broom:before{content:"\f11d"}.el-brush:before{content:"\f11e"}.el-bulb:before{content:"\f11f"}.el-bullhorn:before{content:"\f120"}.el-calendar-sign:before{content:"\f121"}.el-calendar:before{content:"\f122"}.el-camera:before{content:"\f123"}.el-car:before{content:"\f124"}.el-caret-down:before{content:"\f125"}.el-caret-left:before{content:"\f126"}.el-caret-right:before{content:"\f127"}.el-caret-up:before{content:"\f128"}.el-cc:before{content:"\f129"}.el-certificate:before{content:"\f12a"}.el-check-empty:before{content:"\f12b"}.el-check:before{content:"\f12c"}.el-chevron-down:before{content:"\f12d"}.el-chevron-left:before{content:"\f12e"}.el-chevron-right:before{content:"\f12f"}.el-chevron-up:before{content:"\f130"}.el-child:before{content:"\f131"}.el-circle-arrow-down:before{content:"\f132"}.el-circle-arrow-left:before{content:"\f133"}.el-circle-arrow-right:before{content:"\f134"}.el-circle-arrow-up:before{content:"\f135"}.el-cloud-alt:before{content:"\f136"}.el-cloud:before{content:"\f137"}.el-cog-alt:before{content:"\f138"}.el-cog:before{content:"\f139"}.el-cogs:before{content:"\f13a"}.el-comment-alt:before{content:"\f13b"}.el-comment:before{content:"\f13c"}.el-compass-alt:before{content:"\f13d"}.el-compass:before{content:"\f13e"}.el-credit-card:before{content:"\f13f"}.el-css:before{content:"\f140"}.el-dashboard:before{content:"\f141"}.el-delicious:before{content:"\f142"}.el-deviantart:before{content:"\f143"}.el-digg:before{content:"\f144"}.el-download-alt:before{content:"\f145"}.el-download:before{content:"\f146"}.el-dribbble:before{content:"\f147"}.el-edit:before{content:"\f148"}.el-eject:before{content:"\f149"}.el-envelope-alt:before{content:"\f14a"}.el-envelope:before{content:"\f14b"}.el-error-alt:before{content:"\f14c"}.el-error:before{content:"\f14d"}.el-eur:before{content:"\f14e"}.el-exclamation-sign:before{content:"\f14f"}.el-eye-close:before{content:"\f150"}.el-eye-open:before{content:"\f151"}.el-facebook:before{content:"\f152"}.el-facetime-video:before{content:"\f153"}.el-fast-backward:before{content:"\f154"}.el-fast-forward:before{content:"\f155"}.el-female:before{content:"\f156"}.el-file-alt:before{content:"\f157"}.el-file-edit-alt:before{content:"\f158"}.el-file-edit:before{content:"\f159"}.el-file-new-alt:before{content:"\f15a"}.el-file-new:before{content:"\f15b"}.el-file:before{content:"\f15c"}.el-film:before{content:"\f15d"}.el-filter:before{content:"\f15e"}.el-fire:before{content:"\f15f"}.el-flag-alt:before{content:"\f160"}.el-flag:before{content:"\f161"}.el-flickr:before{content:"\f162"}.el-folder-close:before{content:"\f163"}.el-folder-open:before{content:"\f164"}.el-folder-sign:before{content:"\f165"}.el-folder:before{content:"\f166"}.el-font:before{content:"\f167"}.el-fontsize:before{content:"\f168"}.el-fork:before{content:"\f169"}.el-forward-alt:before{content:"\f16a"}.el-forward:before{content:"\f16b"}.el-foursquare:before{content:"\f16c"}.el-friendfeed-rect:before{content:"\f16d"}.el-friendfeed:before{content:"\f16e"}.el-fullscreen:before{content:"\f16f"}.el-gbp:before{content:"\f170"}.el-gift:before{content:"\f171"}.el-github-text:before{content:"\f172"}.el-github:before{content:"\f173"}.el-glass:before{content:"\f174"}.el-glasses:before{content:"\f175"}.el-globe-alt:before{content:"\f176"}.el-globe:before{content:"\f177"}.el-googleplus:before{content:"\f178"}.el-graph-alt:before{content:"\f179"}.el-graph:before{content:"\f17a"}.el-group-alt:before{content:"\f17b"}.el-group:before{content:"\f17c"}.el-guidedog:before{content:"\f17d"}.el-hand-down:before{content:"\f17e"}.el-hand-left:before{content:"\f17f"}.el-hand-right:before{content:"\f180"}.el-hand-up:before{content:"\f181"}.el-hdd:before{content:"\f182"}.el-headphones:before{content:"\f183"}.el-hearing-impaired:before{content:"\f184"}.el-heart-alt:before{content:"\f185"}.el-heart-empty:before{content:"\f186"}.el-heart:before{content:"\f187"}.el-home-alt:before{content:"\f188"}.el-home:before{content:"\f189"}.el-hourglass:before{content:"\f18a"}.el-idea-alt:before{content:"\f18b"}.el-idea:before{content:"\f18c"}.el-inbox-alt:before{content:"\f18d"}.el-inbox-box:before{content:"\f18e"}.el-inbox:before{content:"\f18f"}.el-indent-left:before{content:"\f190"}.el-indent-right:before{content:"\f191"}.el-info-circle:before{content:"\f192"}.el-instagram:before{content:"\f193"}.el-iphone-home:before{content:"\f194"}.el-italic:before{content:"\f195"}.el-key:before{content:"\f196"}.el-laptop-alt:before{content:"\f197"}.el-laptop:before{content:"\f198"}.el-lastfm:before{content:"\f199"}.el-leaf:before{content:"\f19a"}.el-lines:before{content:"\f19b"}.el-link:before{content:"\f19c"}.el-linkedin:before{content:"\f19d"}.el-list-alt:before{content:"\f19e"}.el-list:before{content:"\f19f"}.el-livejournal:before{content:"\f1a0"}.el-lock-alt:before{content:"\f1a1"}.el-lock:before{content:"\f1a2"}.el-magic:before{content:"\f1a3"}.el-magnet:before{content:"\f1a4"}.el-male:before{content:"\f1a5"}.el-map-marker-alt:before{content:"\f1a6"}.el-map-marker:before{content:"\f1a7"}.el-mic-alt:before{content:"\f1a8"}.el-mic:before{content:"\f1a9"}.el-minus-sign:before{content:"\f1aa"}.el-minus:before{content:"\f1ab"}.el-move:before{content:"\f1ac"}.el-music:before{content:"\f1ad"}.el-myspace:before{content:"\f1ae"}.el-network:before{content:"\f1af"}.el-off:before{content:"\f1b0"}.el-ok-circle:before{content:"\f1b1"}.el-ok-sign:before{content:"\f1b2"}.el-ok:before{content:"\f1b3"}.el-opensource:before{content:"\f1b4"}.el-paper-clip-alt:before{content:"\f1b5"}.el-paper-clip:before{content:"\f1b6"}.el-path:before{content:"\f1b7"}.el-pause-alt:before{content:"\f1b8"}.el-pause:before{content:"\f1b9"}.el-pencil-alt:before{content:"\f1ba"}.el-pencil:before{content:"\f1bb"}.el-person:before{content:"\f1bc"}.el-phone-alt:before{content:"\f1bd"}.el-phone:before{content:"\f1be"}.el-photo-alt:before{content:"\f1bf"}.el-photo:before{content:"\f1c0"}.el-picasa:before{content:"\f1c1"}.el-picture:before{content:"\f1c2"}.el-pinterest:before{content:"\f1c3"}.el-plane:before{content:"\f1c4"}.el-play-alt:before{content:"\f1c5"}.el-play-circle:before{content:"\f1c6"}.el-play:before{content:"\f1c7"}.el-plurk-alt:before{content:"\f1c8"}.el-plurk:before{content:"\f1c9"}.el-plus-sign:before{content:"\f1ca"}.el-plus:before{content:"\f1cb"}.el-podcast:before{content:"\f1cc"}.el-print:before{content:"\f1cd"}.el-puzzle:before{content:"\f1ce"}.el-qrcode:before{content:"\f1cf"}.el-question-sign:before{content:"\f1d0"}.el-question:before{content:"\f1d1"}.el-quote-alt:before{content:"\f1d2"}.el-quote-right-alt:before{content:"\f1d3"}.el-quote-right:before{content:"\f1d4"}.el-quotes:before{content:"\f1d5"}.el-random:before{content:"\f1d6"}.el-record:before{content:"\f1d7"}.el-reddit:before{content:"\f1d8"}.el-redux:before{content:"\f1d9"}.el-refresh:before{content:"\f1da"}.el-remove-circle:before{content:"\f1db"}.el-remove-sign:before{content:"\f1dc"}.el-remove:before{content:"\f1dd"}.el-repeat-alt:before{content:"\f1de"}.el-repeat:before{content:"\f1df"}.el-resize-full:before{content:"\f1e0"}.el-resize-horizontal:before{content:"\f1e1"}.el-resize-small:before{content:"\f1e2"}.el-resize-vertical:before{content:"\f1e3"}.el-return-key:before{content:"\f1e4"}.el-retweet:before{content:"\f1e5"}.el-reverse-alt:before{content:"\f1e6"}.el-road:before{content:"\f1e7"}.el-rss:before{content:"\f1e8"}.el-scissors:before{content:"\f1e9"}.el-screen-alt:before{content:"\f1ea"}.el-screen:before{content:"\f1eb"}.el-screenshot:before{content:"\f1ec"}.el-search-alt:before{content:"\f1ed"}.el-search:before{content:"\f1ee"}.el-share-alt:before{content:"\f1ef"}.el-share:before{content:"\f1f0"}.el-shopping-cart-sign:before{content:"\f1f1"}.el-shopping-cart:before{content:"\f1f2"}.el-signal:before{content:"\f1f3"}.el-skype:before{content:"\f1f4"}.el-slideshare:before{content:"\f1f5"}.el-smiley-alt:before{content:"\f1f6"}.el-smiley:before{content:"\f1f7"}.el-soundcloud:before{content:"\f1f8"}.el-speaker:before{content:"\f1f9"}.el-spotify:before{content:"\f1fa"}.el-stackoverflow:before{content:"\f1fb"}.el-star-alt:before{content:"\f1fc"}.el-star-empty:before{content:"\f1fd"}.el-star:before{content:"\f1fe"}.el-step-backward:before{content:"\f1ff"}.el-step-forward:before{content:"\f200"}.el-stop-alt:before{content:"\f201"}.el-stop:before{content:"\f202"}.el-stumbleupon:before{content:"\f203"}.el-tag:before{content:"\f204"}.el-tags:before{content:"\f205"}.el-tasks:before{content:"\f206"}.el-text-height:before{content:"\f207"}.el-text-width:before{content:"\f208"}.el-th-large:before{content:"\f209"}.el-th-list:before{content:"\f20a"}.el-th:before{content:"\f20b"}.el-thumbs-down:before{content:"\f20c"}.el-thumbs-up:before{content:"\f20d"}.el-time-alt:before{content:"\f20e"}.el-time:before{content:"\f20f"}.el-tint:before{content:"\f210"}.el-torso:before{content:"\f211"}.el-trash-alt:before{content:"\f212"}.el-trash:before{content:"\f213"}.el-tumblr:before{content:"\f214"}.el-twitter:before{content:"\f215"}.el-universal-access:before{content:"\f216"}.el-unlock-alt:before{content:"\f217"}.el-unlock:before{content:"\f218"}.el-upload:before{content:"\f219"}.el-usd:before{content:"\f21a"}.el-user:before{content:"\f21b"}.el-viadeo:before{content:"\f21c"}.el-video-alt:before{content:"\f21d"}.el-video-chat:before{content:"\f21e"}.el-video:before{content:"\f21f"}.el-view-mode:before{content:"\f220"}.el-vimeo:before{content:"\f221"}.el-vkontakte:before{content:"\f222"}.el-volume-down:before{content:"\f223"}.el-volume-off:before{content:"\f224"}.el-volume-up:before{content:"\f225"}.el-w3c:before{content:"\f226"}.el-warning-sign:before{content:"\f227"}.el-website-alt:before{content:"\f228"}.el-website:before{content:"\f229"}.el-wheelchair:before{content:"\f22a"}.el-wordpress:before{content:"\f22b"}.el-wrench-alt:before{content:"\f22c"}.el-wrench:before{content:"\f22d"}.el-youtube:before{content:"\f22e"}.el-zoom-in:before{content:"\f22f"}.el-zoom-out:before{content:"\f230"} diff --git a/Public/styles/elusive-icons/fonts/elusiveicons-webfont.eot b/Public/styles/elusive-icons/fonts/elusiveicons-webfont.eot new file mode 100644 index 0000000..f42a001 Binary files /dev/null and b/Public/styles/elusive-icons/fonts/elusiveicons-webfont.eot differ diff --git a/Public/styles/elusive-icons/fonts/elusiveicons-webfont.svg b/Public/styles/elusive-icons/fonts/elusiveicons-webfont.svg new file mode 100644 index 0000000..1310e6c --- /dev/null +++ b/Public/styles/elusive-icons/fonts/elusiveicons-webfont.svg @@ -0,0 +1,931 @@ + + + + +Created by FontForge 20120731 at Thu Feb 19 13:35:54 2015 + By Dovy + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Public/styles/elusive-icons/fonts/elusiveicons-webfont.ttf b/Public/styles/elusive-icons/fonts/elusiveicons-webfont.ttf new file mode 100644 index 0000000..b6fe85d Binary files /dev/null and b/Public/styles/elusive-icons/fonts/elusiveicons-webfont.ttf differ diff --git a/Public/styles/elusive-icons/fonts/elusiveicons-webfont.woff b/Public/styles/elusive-icons/fonts/elusiveicons-webfont.woff new file mode 100644 index 0000000..1e0487d Binary files /dev/null and b/Public/styles/elusive-icons/fonts/elusiveicons-webfont.woff differ diff --git a/Public/styles/elusive-icons/less/animated.less b/Public/styles/elusive-icons/less/animated.less new file mode 100644 index 0000000..c4efeb7 --- /dev/null +++ b/Public/styles/elusive-icons/less/animated.less @@ -0,0 +1,34 @@ +// Animated Icons +// -------------------------- + +.@{el-css-prefix}-spin { + -webkit-animation: el-spin 2s infinite linear; + animation: el-spin 2s infinite linear; +} + +.@{el-css-prefix}-pulse { + -webkit-animation: el-spin 1s infinite steps(8); + animation: el-spin 1s infinite steps(8); +} + +@-webkit-keyframes el-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +@keyframes el-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} diff --git a/Public/styles/elusive-icons/less/bordered-pulled.less b/Public/styles/elusive-icons/less/bordered-pulled.less new file mode 100644 index 0000000..5896ddf --- /dev/null +++ b/Public/styles/elusive-icons/less/bordered-pulled.less @@ -0,0 +1,16 @@ +// Bordered & Pulled +// ------------------------- + +.@{el-css-prefix}-border { + padding: .2em .25em .15em; + border: solid .08em @el-border-color; + border-radius: .1em; +} + +.pull-right { float: right; } +.pull-left { float: left; } + +.@{el-css-prefix} { + &.pull-left { margin-right: .3em; } + &.pull-right { margin-left: .3em; } +} diff --git a/Public/styles/elusive-icons/less/core.less b/Public/styles/elusive-icons/less/core.less new file mode 100644 index 0000000..f29ac6e --- /dev/null +++ b/Public/styles/elusive-icons/less/core.less @@ -0,0 +1,13 @@ +// Base Class Definition +// ------------------------- + +.@{el-css-prefix} { + display: inline-block; + font: normal normal normal @el-font-size-base/1 'Elusive-Icons'; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + transform: translate(0, 0); // ensures no half-pixel rendering in firefox + +} diff --git a/Public/styles/elusive-icons/less/elusive-icons.less b/Public/styles/elusive-icons/less/elusive-icons.less new file mode 100644 index 0000000..f352477 --- /dev/null +++ b/Public/styles/elusive-icons/less/elusive-icons.less @@ -0,0 +1,17 @@ +/*! + * Elusive Icons 2.0.0 by @ReduxFramework - http://elusiveicons.com - @reduxframework + * License - http://elusiveicons.com/license (Font: SIL OFL 1.1, CSS: MIT License) + */ + +@import "variables.less"; +@import "mixins.less"; +@import "path.less"; +@import "core.less"; +@import "larger.less"; +@import "fixed-width.less"; +@import "list.less"; +@import "bordered-pulled.less"; +@import "animated.less"; +@import "rotated-flipped.less"; +@import "stacked.less"; +@import "icons.less"; diff --git a/Public/styles/elusive-icons/less/fixed-width.less b/Public/styles/elusive-icons/less/fixed-width.less new file mode 100644 index 0000000..9f6a79a --- /dev/null +++ b/Public/styles/elusive-icons/less/fixed-width.less @@ -0,0 +1,6 @@ +// Fixed Width Icons +// ------------------------- +.@{el-css-prefix}-fw { + width: (18em / 14); + text-align: center; +} diff --git a/Public/styles/elusive-icons/less/icons.less b/Public/styles/elusive-icons/less/icons.less new file mode 100644 index 0000000..908e3fa --- /dev/null +++ b/Public/styles/elusive-icons/less/icons.less @@ -0,0 +1,307 @@ +/* Elusive Icons uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ + +.@{el-css-prefix}-address-book-alt:before { content: @el-var-address-book-alt; } +.@{el-css-prefix}-address-book:before { content: @el-var-address-book; } +.@{el-css-prefix}-adjust-alt:before { content: @el-var-adjust-alt; } +.@{el-css-prefix}-adjust:before { content: @el-var-adjust; } +.@{el-css-prefix}-adult:before { content: @el-var-adult; } +.@{el-css-prefix}-align-center:before { content: @el-var-align-center; } +.@{el-css-prefix}-align-justify:before { content: @el-var-align-justify; } +.@{el-css-prefix}-align-left:before { content: @el-var-align-left; } +.@{el-css-prefix}-align-right:before { content: @el-var-align-right; } +.@{el-css-prefix}-arrow-down:before { content: @el-var-arrow-down; } +.@{el-css-prefix}-arrow-left:before { content: @el-var-arrow-left; } +.@{el-css-prefix}-arrow-right:before { content: @el-var-arrow-right; } +.@{el-css-prefix}-arrow-up:before { content: @el-var-arrow-up; } +.@{el-css-prefix}-asl:before { content: @el-var-asl; } +.@{el-css-prefix}-asterisk:before { content: @el-var-asterisk; } +.@{el-css-prefix}-backward:before { content: @el-var-backward; } +.@{el-css-prefix}-ban-circle:before { content: @el-var-ban-circle; } +.@{el-css-prefix}-barcode:before { content: @el-var-barcode; } +.@{el-css-prefix}-behance:before { content: @el-var-behance; } +.@{el-css-prefix}-bell:before { content: @el-var-bell; } +.@{el-css-prefix}-blind:before { content: @el-var-blind; } +.@{el-css-prefix}-blogger:before { content: @el-var-blogger; } +.@{el-css-prefix}-bold:before { content: @el-var-bold; } +.@{el-css-prefix}-book:before { content: @el-var-book; } +.@{el-css-prefix}-bookmark-empty:before { content: @el-var-bookmark-empty; } +.@{el-css-prefix}-bookmark:before { content: @el-var-bookmark; } +.@{el-css-prefix}-braille:before { content: @el-var-braille; } +.@{el-css-prefix}-briefcase:before { content: @el-var-briefcase; } +.@{el-css-prefix}-broom:before { content: @el-var-broom; } +.@{el-css-prefix}-brush:before { content: @el-var-brush; } +.@{el-css-prefix}-bulb:before { content: @el-var-bulb; } +.@{el-css-prefix}-bullhorn:before { content: @el-var-bullhorn; } +.@{el-css-prefix}-calendar-sign:before { content: @el-var-calendar-sign; } +.@{el-css-prefix}-calendar:before { content: @el-var-calendar; } +.@{el-css-prefix}-camera:before { content: @el-var-camera; } +.@{el-css-prefix}-car:before { content: @el-var-car; } +.@{el-css-prefix}-caret-down:before { content: @el-var-caret-down; } +.@{el-css-prefix}-caret-left:before { content: @el-var-caret-left; } +.@{el-css-prefix}-caret-right:before { content: @el-var-caret-right; } +.@{el-css-prefix}-caret-up:before { content: @el-var-caret-up; } +.@{el-css-prefix}-cc:before { content: @el-var-cc; } +.@{el-css-prefix}-certificate:before { content: @el-var-certificate; } +.@{el-css-prefix}-check-empty:before { content: @el-var-check-empty; } +.@{el-css-prefix}-check:before { content: @el-var-check; } +.@{el-css-prefix}-chevron-down:before { content: @el-var-chevron-down; } +.@{el-css-prefix}-chevron-left:before { content: @el-var-chevron-left; } +.@{el-css-prefix}-chevron-right:before { content: @el-var-chevron-right; } +.@{el-css-prefix}-chevron-up:before { content: @el-var-chevron-up; } +.@{el-css-prefix}-child:before { content: @el-var-child; } +.@{el-css-prefix}-circle-arrow-down:before { content: @el-var-circle-arrow-down; } +.@{el-css-prefix}-circle-arrow-left:before { content: @el-var-circle-arrow-left; } +.@{el-css-prefix}-circle-arrow-right:before { content: @el-var-circle-arrow-right; } +.@{el-css-prefix}-circle-arrow-up:before { content: @el-var-circle-arrow-up; } +.@{el-css-prefix}-cloud-alt:before { content: @el-var-cloud-alt; } +.@{el-css-prefix}-cloud:before { content: @el-var-cloud; } +.@{el-css-prefix}-cog-alt:before { content: @el-var-cog-alt; } +.@{el-css-prefix}-cog:before { content: @el-var-cog; } +.@{el-css-prefix}-cogs:before { content: @el-var-cogs; } +.@{el-css-prefix}-comment-alt:before { content: @el-var-comment-alt; } +.@{el-css-prefix}-comment:before { content: @el-var-comment; } +.@{el-css-prefix}-compass-alt:before { content: @el-var-compass-alt; } +.@{el-css-prefix}-compass:before { content: @el-var-compass; } +.@{el-css-prefix}-credit-card:before { content: @el-var-credit-card; } +.@{el-css-prefix}-css:before { content: @el-var-css; } +.@{el-css-prefix}-dashboard:before { content: @el-var-dashboard; } +.@{el-css-prefix}-delicious:before { content: @el-var-delicious; } +.@{el-css-prefix}-deviantart:before { content: @el-var-deviantart; } +.@{el-css-prefix}-digg:before { content: @el-var-digg; } +.@{el-css-prefix}-download-alt:before { content: @el-var-download-alt; } +.@{el-css-prefix}-download:before { content: @el-var-download; } +.@{el-css-prefix}-dribbble:before { content: @el-var-dribbble; } +.@{el-css-prefix}-edit:before { content: @el-var-edit; } +.@{el-css-prefix}-eject:before { content: @el-var-eject; } +.@{el-css-prefix}-envelope-alt:before { content: @el-var-envelope-alt; } +.@{el-css-prefix}-envelope:before { content: @el-var-envelope; } +.@{el-css-prefix}-error-alt:before { content: @el-var-error-alt; } +.@{el-css-prefix}-error:before { content: @el-var-error; } +.@{el-css-prefix}-eur:before { content: @el-var-eur; } +.@{el-css-prefix}-exclamation-sign:before { content: @el-var-exclamation-sign; } +.@{el-css-prefix}-eye-close:before { content: @el-var-eye-close; } +.@{el-css-prefix}-eye-open:before { content: @el-var-eye-open; } +.@{el-css-prefix}-facebook:before { content: @el-var-facebook; } +.@{el-css-prefix}-facetime-video:before { content: @el-var-facetime-video; } +.@{el-css-prefix}-fast-backward:before { content: @el-var-fast-backward; } +.@{el-css-prefix}-fast-forward:before { content: @el-var-fast-forward; } +.@{el-css-prefix}-female:before { content: @el-var-female; } +.@{el-css-prefix}-file-alt:before { content: @el-var-file-alt; } +.@{el-css-prefix}-file-edit-alt:before { content: @el-var-file-edit-alt; } +.@{el-css-prefix}-file-edit:before { content: @el-var-file-edit; } +.@{el-css-prefix}-file-new-alt:before { content: @el-var-file-new-alt; } +.@{el-css-prefix}-file-new:before { content: @el-var-file-new; } +.@{el-css-prefix}-file:before { content: @el-var-file; } +.@{el-css-prefix}-film:before { content: @el-var-film; } +.@{el-css-prefix}-filter:before { content: @el-var-filter; } +.@{el-css-prefix}-fire:before { content: @el-var-fire; } +.@{el-css-prefix}-flag-alt:before { content: @el-var-flag-alt; } +.@{el-css-prefix}-flag:before { content: @el-var-flag; } +.@{el-css-prefix}-flickr:before { content: @el-var-flickr; } +.@{el-css-prefix}-folder-close:before { content: @el-var-folder-close; } +.@{el-css-prefix}-folder-open:before { content: @el-var-folder-open; } +.@{el-css-prefix}-folder-sign:before { content: @el-var-folder-sign; } +.@{el-css-prefix}-folder:before { content: @el-var-folder; } +.@{el-css-prefix}-font:before { content: @el-var-font; } +.@{el-css-prefix}-fontsize:before { content: @el-var-fontsize; } +.@{el-css-prefix}-fork:before { content: @el-var-fork; } +.@{el-css-prefix}-forward-alt:before { content: @el-var-forward-alt; } +.@{el-css-prefix}-forward:before { content: @el-var-forward; } +.@{el-css-prefix}-foursquare:before { content: @el-var-foursquare; } +.@{el-css-prefix}-friendfeed-rect:before { content: @el-var-friendfeed-rect; } +.@{el-css-prefix}-friendfeed:before { content: @el-var-friendfeed; } +.@{el-css-prefix}-fullscreen:before { content: @el-var-fullscreen; } +.@{el-css-prefix}-gbp:before { content: @el-var-gbp; } +.@{el-css-prefix}-gift:before { content: @el-var-gift; } +.@{el-css-prefix}-github-text:before { content: @el-var-github-text; } +.@{el-css-prefix}-github:before { content: @el-var-github; } +.@{el-css-prefix}-glass:before { content: @el-var-glass; } +.@{el-css-prefix}-glasses:before { content: @el-var-glasses; } +.@{el-css-prefix}-globe-alt:before { content: @el-var-globe-alt; } +.@{el-css-prefix}-globe:before { content: @el-var-globe; } +.@{el-css-prefix}-googleplus:before { content: @el-var-googleplus; } +.@{el-css-prefix}-graph-alt:before { content: @el-var-graph-alt; } +.@{el-css-prefix}-graph:before { content: @el-var-graph; } +.@{el-css-prefix}-group-alt:before { content: @el-var-group-alt; } +.@{el-css-prefix}-group:before { content: @el-var-group; } +.@{el-css-prefix}-guidedog:before { content: @el-var-guidedog; } +.@{el-css-prefix}-hand-down:before { content: @el-var-hand-down; } +.@{el-css-prefix}-hand-left:before { content: @el-var-hand-left; } +.@{el-css-prefix}-hand-right:before { content: @el-var-hand-right; } +.@{el-css-prefix}-hand-up:before { content: @el-var-hand-up; } +.@{el-css-prefix}-hdd:before { content: @el-var-hdd; } +.@{el-css-prefix}-headphones:before { content: @el-var-headphones; } +.@{el-css-prefix}-hearing-impaired:before { content: @el-var-hearing-impaired; } +.@{el-css-prefix}-heart-alt:before { content: @el-var-heart-alt; } +.@{el-css-prefix}-heart-empty:before { content: @el-var-heart-empty; } +.@{el-css-prefix}-heart:before { content: @el-var-heart; } +.@{el-css-prefix}-home-alt:before { content: @el-var-home-alt; } +.@{el-css-prefix}-home:before { content: @el-var-home; } +.@{el-css-prefix}-hourglass:before { content: @el-var-hourglass; } +.@{el-css-prefix}-idea-alt:before { content: @el-var-idea-alt; } +.@{el-css-prefix}-idea:before { content: @el-var-idea; } +.@{el-css-prefix}-inbox-alt:before { content: @el-var-inbox-alt; } +.@{el-css-prefix}-inbox-box:before { content: @el-var-inbox-box; } +.@{el-css-prefix}-inbox:before { content: @el-var-inbox; } +.@{el-css-prefix}-indent-left:before { content: @el-var-indent-left; } +.@{el-css-prefix}-indent-right:before { content: @el-var-indent-right; } +.@{el-css-prefix}-info-circle:before { content: @el-var-info-circle; } +.@{el-css-prefix}-instagram:before { content: @el-var-instagram; } +.@{el-css-prefix}-iphone-home:before { content: @el-var-iphone-home; } +.@{el-css-prefix}-italic:before { content: @el-var-italic; } +.@{el-css-prefix}-key:before { content: @el-var-key; } +.@{el-css-prefix}-laptop-alt:before { content: @el-var-laptop-alt; } +.@{el-css-prefix}-laptop:before { content: @el-var-laptop; } +.@{el-css-prefix}-lastfm:before { content: @el-var-lastfm; } +.@{el-css-prefix}-leaf:before { content: @el-var-leaf; } +.@{el-css-prefix}-lines:before { content: @el-var-lines; } +.@{el-css-prefix}-link:before { content: @el-var-link; } +.@{el-css-prefix}-linkedin:before { content: @el-var-linkedin; } +.@{el-css-prefix}-list-alt:before { content: @el-var-list-alt; } +.@{el-css-prefix}-list:before { content: @el-var-list; } +.@{el-css-prefix}-livejournal:before { content: @el-var-livejournal; } +.@{el-css-prefix}-lock-alt:before { content: @el-var-lock-alt; } +.@{el-css-prefix}-lock:before { content: @el-var-lock; } +.@{el-css-prefix}-magic:before { content: @el-var-magic; } +.@{el-css-prefix}-magnet:before { content: @el-var-magnet; } +.@{el-css-prefix}-male:before { content: @el-var-male; } +.@{el-css-prefix}-map-marker-alt:before { content: @el-var-map-marker-alt; } +.@{el-css-prefix}-map-marker:before { content: @el-var-map-marker; } +.@{el-css-prefix}-mic-alt:before { content: @el-var-mic-alt; } +.@{el-css-prefix}-mic:before { content: @el-var-mic; } +.@{el-css-prefix}-minus-sign:before { content: @el-var-minus-sign; } +.@{el-css-prefix}-minus:before { content: @el-var-minus; } +.@{el-css-prefix}-move:before { content: @el-var-move; } +.@{el-css-prefix}-music:before { content: @el-var-music; } +.@{el-css-prefix}-myspace:before { content: @el-var-myspace; } +.@{el-css-prefix}-network:before { content: @el-var-network; } +.@{el-css-prefix}-off:before { content: @el-var-off; } +.@{el-css-prefix}-ok-circle:before { content: @el-var-ok-circle; } +.@{el-css-prefix}-ok-sign:before { content: @el-var-ok-sign; } +.@{el-css-prefix}-ok:before { content: @el-var-ok; } +.@{el-css-prefix}-opensource:before { content: @el-var-opensource; } +.@{el-css-prefix}-paper-clip-alt:before { content: @el-var-paper-clip-alt; } +.@{el-css-prefix}-paper-clip:before { content: @el-var-paper-clip; } +.@{el-css-prefix}-path:before { content: @el-var-path; } +.@{el-css-prefix}-pause-alt:before { content: @el-var-pause-alt; } +.@{el-css-prefix}-pause:before { content: @el-var-pause; } +.@{el-css-prefix}-pencil-alt:before { content: @el-var-pencil-alt; } +.@{el-css-prefix}-pencil:before { content: @el-var-pencil; } +.@{el-css-prefix}-person:before { content: @el-var-person; } +.@{el-css-prefix}-phone-alt:before { content: @el-var-phone-alt; } +.@{el-css-prefix}-phone:before { content: @el-var-phone; } +.@{el-css-prefix}-photo-alt:before { content: @el-var-photo-alt; } +.@{el-css-prefix}-photo:before { content: @el-var-photo; } +.@{el-css-prefix}-picasa:before { content: @el-var-picasa; } +.@{el-css-prefix}-picture:before { content: @el-var-picture; } +.@{el-css-prefix}-pinterest:before { content: @el-var-pinterest; } +.@{el-css-prefix}-plane:before { content: @el-var-plane; } +.@{el-css-prefix}-play-alt:before { content: @el-var-play-alt; } +.@{el-css-prefix}-play-circle:before { content: @el-var-play-circle; } +.@{el-css-prefix}-play:before { content: @el-var-play; } +.@{el-css-prefix}-plurk-alt:before { content: @el-var-plurk-alt; } +.@{el-css-prefix}-plurk:before { content: @el-var-plurk; } +.@{el-css-prefix}-plus-sign:before { content: @el-var-plus-sign; } +.@{el-css-prefix}-plus:before { content: @el-var-plus; } +.@{el-css-prefix}-podcast:before { content: @el-var-podcast; } +.@{el-css-prefix}-print:before { content: @el-var-print; } +.@{el-css-prefix}-puzzle:before { content: @el-var-puzzle; } +.@{el-css-prefix}-qrcode:before { content: @el-var-qrcode; } +.@{el-css-prefix}-question-sign:before { content: @el-var-question-sign; } +.@{el-css-prefix}-question:before { content: @el-var-question; } +.@{el-css-prefix}-quote-alt:before { content: @el-var-quote-alt; } +.@{el-css-prefix}-quote-right-alt:before { content: @el-var-quote-right-alt; } +.@{el-css-prefix}-quote-right:before { content: @el-var-quote-right; } +.@{el-css-prefix}-quotes:before { content: @el-var-quotes; } +.@{el-css-prefix}-random:before { content: @el-var-random; } +.@{el-css-prefix}-record:before { content: @el-var-record; } +.@{el-css-prefix}-reddit:before { content: @el-var-reddit; } +.@{el-css-prefix}-redux:before { content: @el-var-redux; } +.@{el-css-prefix}-refresh:before { content: @el-var-refresh; } +.@{el-css-prefix}-remove-circle:before { content: @el-var-remove-circle; } +.@{el-css-prefix}-remove-sign:before { content: @el-var-remove-sign; } +.@{el-css-prefix}-remove:before { content: @el-var-remove; } +.@{el-css-prefix}-repeat-alt:before { content: @el-var-repeat-alt; } +.@{el-css-prefix}-repeat:before { content: @el-var-repeat; } +.@{el-css-prefix}-resize-full:before { content: @el-var-resize-full; } +.@{el-css-prefix}-resize-horizontal:before { content: @el-var-resize-horizontal; } +.@{el-css-prefix}-resize-small:before { content: @el-var-resize-small; } +.@{el-css-prefix}-resize-vertical:before { content: @el-var-resize-vertical; } +.@{el-css-prefix}-return-key:before { content: @el-var-return-key; } +.@{el-css-prefix}-retweet:before { content: @el-var-retweet; } +.@{el-css-prefix}-reverse-alt:before { content: @el-var-reverse-alt; } +.@{el-css-prefix}-road:before { content: @el-var-road; } +.@{el-css-prefix}-rss:before { content: @el-var-rss; } +.@{el-css-prefix}-scissors:before { content: @el-var-scissors; } +.@{el-css-prefix}-screen-alt:before { content: @el-var-screen-alt; } +.@{el-css-prefix}-screen:before { content: @el-var-screen; } +.@{el-css-prefix}-screenshot:before { content: @el-var-screenshot; } +.@{el-css-prefix}-search-alt:before { content: @el-var-search-alt; } +.@{el-css-prefix}-search:before { content: @el-var-search; } +.@{el-css-prefix}-share-alt:before { content: @el-var-share-alt; } +.@{el-css-prefix}-share:before { content: @el-var-share; } +.@{el-css-prefix}-shopping-cart-sign:before { content: @el-var-shopping-cart-sign; } +.@{el-css-prefix}-shopping-cart:before { content: @el-var-shopping-cart; } +.@{el-css-prefix}-signal:before { content: @el-var-signal; } +.@{el-css-prefix}-skype:before { content: @el-var-skype; } +.@{el-css-prefix}-slideshare:before { content: @el-var-slideshare; } +.@{el-css-prefix}-smiley-alt:before { content: @el-var-smiley-alt; } +.@{el-css-prefix}-smiley:before { content: @el-var-smiley; } +.@{el-css-prefix}-soundcloud:before { content: @el-var-soundcloud; } +.@{el-css-prefix}-speaker:before { content: @el-var-speaker; } +.@{el-css-prefix}-spotify:before { content: @el-var-spotify; } +.@{el-css-prefix}-stackoverflow:before { content: @el-var-stackoverflow; } +.@{el-css-prefix}-star-alt:before { content: @el-var-star-alt; } +.@{el-css-prefix}-star-empty:before { content: @el-var-star-empty; } +.@{el-css-prefix}-star:before { content: @el-var-star; } +.@{el-css-prefix}-step-backward:before { content: @el-var-step-backward; } +.@{el-css-prefix}-step-forward:before { content: @el-var-step-forward; } +.@{el-css-prefix}-stop-alt:before { content: @el-var-stop-alt; } +.@{el-css-prefix}-stop:before { content: @el-var-stop; } +.@{el-css-prefix}-stumbleupon:before { content: @el-var-stumbleupon; } +.@{el-css-prefix}-tag:before { content: @el-var-tag; } +.@{el-css-prefix}-tags:before { content: @el-var-tags; } +.@{el-css-prefix}-tasks:before { content: @el-var-tasks; } +.@{el-css-prefix}-text-height:before { content: @el-var-text-height; } +.@{el-css-prefix}-text-width:before { content: @el-var-text-width; } +.@{el-css-prefix}-th-large:before { content: @el-var-th-large; } +.@{el-css-prefix}-th-list:before { content: @el-var-th-list; } +.@{el-css-prefix}-th:before { content: @el-var-th; } +.@{el-css-prefix}-thumbs-down:before { content: @el-var-thumbs-down; } +.@{el-css-prefix}-thumbs-up:before { content: @el-var-thumbs-up; } +.@{el-css-prefix}-time-alt:before { content: @el-var-time-alt; } +.@{el-css-prefix}-time:before { content: @el-var-time; } +.@{el-css-prefix}-tint:before { content: @el-var-tint; } +.@{el-css-prefix}-torso:before { content: @el-var-torso; } +.@{el-css-prefix}-trash-alt:before { content: @el-var-trash-alt; } +.@{el-css-prefix}-trash:before { content: @el-var-trash; } +.@{el-css-prefix}-tumblr:before { content: @el-var-tumblr; } +.@{el-css-prefix}-twitter:before { content: @el-var-twitter; } +.@{el-css-prefix}-universal-access:before { content: @el-var-universal-access; } +.@{el-css-prefix}-unlock-alt:before { content: @el-var-unlock-alt; } +.@{el-css-prefix}-unlock:before { content: @el-var-unlock; } +.@{el-css-prefix}-upload:before { content: @el-var-upload; } +.@{el-css-prefix}-usd:before { content: @el-var-usd; } +.@{el-css-prefix}-user:before { content: @el-var-user; } +.@{el-css-prefix}-viadeo:before { content: @el-var-viadeo; } +.@{el-css-prefix}-video-alt:before { content: @el-var-video-alt; } +.@{el-css-prefix}-video-chat:before { content: @el-var-video-chat; } +.@{el-css-prefix}-video:before { content: @el-var-video; } +.@{el-css-prefix}-view-mode:before { content: @el-var-view-mode; } +.@{el-css-prefix}-vimeo:before { content: @el-var-vimeo; } +.@{el-css-prefix}-vkontakte:before { content: @el-var-vkontakte; } +.@{el-css-prefix}-volume-down:before { content: @el-var-volume-down; } +.@{el-css-prefix}-volume-off:before { content: @el-var-volume-off; } +.@{el-css-prefix}-volume-up:before { content: @el-var-volume-up; } +.@{el-css-prefix}-w3c:before { content: @el-var-w3c; } +.@{el-css-prefix}-warning-sign:before { content: @el-var-warning-sign; } +.@{el-css-prefix}-website-alt:before { content: @el-var-website-alt; } +.@{el-css-prefix}-website:before { content: @el-var-website; } +.@{el-css-prefix}-wheelchair:before { content: @el-var-wheelchair; } +.@{el-css-prefix}-wordpress:before { content: @el-var-wordpress; } +.@{el-css-prefix}-wrench-alt:before { content: @el-var-wrench-alt; } +.@{el-css-prefix}-wrench:before { content: @el-var-wrench; } +.@{el-css-prefix}-youtube:before { content: @el-var-youtube; } +.@{el-css-prefix}-zoom-in:before { content: @el-var-zoom-in; } +.@{el-css-prefix}-zoom-out:before { content: @el-var-zoom-out; } diff --git a/Public/styles/elusive-icons/less/larger.less b/Public/styles/elusive-icons/less/larger.less new file mode 100644 index 0000000..b5d8798 --- /dev/null +++ b/Public/styles/elusive-icons/less/larger.less @@ -0,0 +1,13 @@ +// Icon Sizes +// ------------------------- + +/* makes the font 33% larger relative to the icon container */ +.@{el-css-prefix}-lg { + font-size: (4em / 3); + line-height: (3em / 4); + vertical-align: -15%; +} +.@{el-css-prefix}-2x { font-size: 2em; } +.@{el-css-prefix}-3x { font-size: 3em; } +.@{el-css-prefix}-4x { font-size: 4em; } +.@{el-css-prefix}-5x { font-size: 5em; } diff --git a/Public/styles/elusive-icons/less/list.less b/Public/styles/elusive-icons/less/list.less new file mode 100644 index 0000000..24d1136 --- /dev/null +++ b/Public/styles/elusive-icons/less/list.less @@ -0,0 +1,19 @@ +// List Icons +// ------------------------- + +.@{el-css-prefix}-ul { + padding-left: 0; + margin-left: @el-li-width; + list-style-type: none; + > li { position: relative; } +} +.@{el-css-prefix}-li { + position: absolute; + left: -@el-li-width; + width: @el-li-width; + top: (2em / 14); + text-align: center; + &.@{el-css-prefix}-lg { + left: (-@el-li-width + (4em / 14)); + } +} diff --git a/Public/styles/elusive-icons/less/mixins.less b/Public/styles/elusive-icons/less/mixins.less new file mode 100644 index 0000000..4f1ed5b --- /dev/null +++ b/Public/styles/elusive-icons/less/mixins.less @@ -0,0 +1,27 @@ +// Mixins +// -------------------------- + +.el-icon() { + display: inline-block; + font: normal normal normal @el-font-size-base/1 'Elusive-Icons'; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + transform: translate(0, 0); // ensures no half-pixel rendering in firefox + +} + +.el-icon-rotate(@degrees, @rotation) { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation); + -webkit-transform: rotate(@degrees); + -ms-transform: rotate(@degrees); + transform: rotate(@degrees); +} + +.el-icon-flip(@horiz, @vert, @rotation) { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=@rotation, mirror=1); + -webkit-transform: scale(@horiz, @vert); + -ms-transform: scale(@horiz, @vert); + transform: scale(@horiz, @vert); +} diff --git a/Public/styles/elusive-icons/less/path.less b/Public/styles/elusive-icons/less/path.less new file mode 100644 index 0000000..96fd8a9 --- /dev/null +++ b/Public/styles/elusive-icons/less/path.less @@ -0,0 +1,14 @@ +/* FONT PATH + * -------------------------- */ + +@font-face { + font-family: 'Elusive-Icons'; + src: url('@{el-font-path}/elusiveicons-webfont.eot?v=@{el-version}'); + src: url('@{el-font-path}/elusiveicons-webfont.eot?#iefix&v=@{el-version}') format('embedded-opentype'), + //url('@{el-font-path}/elusiveicons-webfont.woff2?v=@{el-version}') format('woff2'), + url('@{el-font-path}/elusiveicons-webfont.woff?v=@{el-version}') format('woff'), + url('@{el-font-path}/elusiveicons-webfont.ttf?v=@{el-version}') format('truetype'), + url('@{el-font-path}/elusiveicons-webfont.svg?v=@{el-version}#elusiveiconsregular') format('svg'); + font-weight: normal; + font-style: normal; +} diff --git a/Public/styles/elusive-icons/less/rotated-flipped.less b/Public/styles/elusive-icons/less/rotated-flipped.less new file mode 100644 index 0000000..26e8259 --- /dev/null +++ b/Public/styles/elusive-icons/less/rotated-flipped.less @@ -0,0 +1,20 @@ +// Rotated & Flipped Icons +// ------------------------- + +.@{el-css-prefix}-rotate-90 { .el-icon-rotate(90deg, 1); } +.@{el-css-prefix}-rotate-180 { .el-icon-rotate(180deg, 2); } +.@{el-css-prefix}-rotate-270 { .el-icon-rotate(270deg, 3); } + +.@{el-css-prefix}-flip-horizontal { .el-icon-flip(-1, 1, 0); } +.@{el-css-prefix}-flip-vertical { .el-icon-flip(1, -1, 2); } + +// Hook for IE8-9 +// ------------------------- + +:root .@{el-css-prefix}-rotate-90, +:root .@{el-css-prefix}-rotate-180, +:root .@{el-css-prefix}-rotate-270, +:root .@{el-css-prefix}-flip-horizontal, +:root .@{el-css-prefix}-flip-vertical { + filter: none; +} diff --git a/Public/styles/elusive-icons/less/stacked.less b/Public/styles/elusive-icons/less/stacked.less new file mode 100644 index 0000000..d244d6c --- /dev/null +++ b/Public/styles/elusive-icons/less/stacked.less @@ -0,0 +1,20 @@ +// Stacked Icons +// ------------------------- + +.@{el-css-prefix}-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.@{el-css-prefix}-stack-1x, .@{el-css-prefix}-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.@{el-css-prefix}-stack-1x { line-height: inherit; } +.@{el-css-prefix}-stack-2x { font-size: 2em; } +.@{el-css-prefix}-inverse { color: @el-inverse; } diff --git a/Public/styles/elusive-icons/less/variables.less b/Public/styles/elusive-icons/less/variables.less new file mode 100644 index 0000000..fab56d3 --- /dev/null +++ b/Public/styles/elusive-icons/less/variables.less @@ -0,0 +1,317 @@ +// Variables +// -------------------------- + +@el-font-path: "../fonts"; +@el-font-size-base: 14px; +//@el-font-path: "//netdna.bootstrapcdn.com/elusive-icons/2.0.0/fonts"; // for referencing Bootstrap CDN font files directly +@el-css-prefix: el; +@el-version: "2.0.0"; +@el-border-color: #eee; +@el-inverse: #fff; +@el-li-width: (30em / 14); + +@el-var-address-book: "\f102"; +@el-var-address-book-alt: "\f101"; +@el-var-adjust: "\f104"; +@el-var-adjust-alt: "\f103"; +@el-var-adult: "\f105"; +@el-var-align-center: "\f106"; +@el-var-align-justify: "\f107"; +@el-var-align-left: "\f108"; +@el-var-align-right: "\f109"; +@el-var-arrow-down: "\f10a"; +@el-var-arrow-left: "\f10b"; +@el-var-arrow-right: "\f10c"; +@el-var-arrow-up: "\f10d"; +@el-var-asl: "\f10e"; +@el-var-asterisk: "\f10f"; +@el-var-backward: "\f110"; +@el-var-ban-circle: "\f111"; +@el-var-barcode: "\f112"; +@el-var-behance: "\f113"; +@el-var-bell: "\f114"; +@el-var-blind: "\f115"; +@el-var-blogger: "\f116"; +@el-var-bold: "\f117"; +@el-var-book: "\f118"; +@el-var-bookmark: "\f11a"; +@el-var-bookmark-empty: "\f119"; +@el-var-braille: "\f11b"; +@el-var-briefcase: "\f11c"; +@el-var-broom: "\f11d"; +@el-var-brush: "\f11e"; +@el-var-bulb: "\f11f"; +@el-var-bullhorn: "\f120"; +@el-var-calendar: "\f122"; +@el-var-calendar-sign: "\f121"; +@el-var-camera: "\f123"; +@el-var-car: "\f124"; +@el-var-caret-down: "\f125"; +@el-var-caret-left: "\f126"; +@el-var-caret-right: "\f127"; +@el-var-caret-up: "\f128"; +@el-var-cc: "\f129"; +@el-var-certificate: "\f12a"; +@el-var-check: "\f12c"; +@el-var-check-empty: "\f12b"; +@el-var-chevron-down: "\f12d"; +@el-var-chevron-left: "\f12e"; +@el-var-chevron-right: "\f12f"; +@el-var-chevron-up: "\f130"; +@el-var-child: "\f131"; +@el-var-circle-arrow-down: "\f132"; +@el-var-circle-arrow-left: "\f133"; +@el-var-circle-arrow-right: "\f134"; +@el-var-circle-arrow-up: "\f135"; +@el-var-cloud: "\f137"; +@el-var-cloud-alt: "\f136"; +@el-var-cog: "\f139"; +@el-var-cog-alt: "\f138"; +@el-var-cogs: "\f13a"; +@el-var-comment: "\f13c"; +@el-var-comment-alt: "\f13b"; +@el-var-compass: "\f13e"; +@el-var-compass-alt: "\f13d"; +@el-var-credit-card: "\f13f"; +@el-var-css: "\f140"; +@el-var-dashboard: "\f141"; +@el-var-delicious: "\f142"; +@el-var-deviantart: "\f143"; +@el-var-digg: "\f144"; +@el-var-download: "\f146"; +@el-var-download-alt: "\f145"; +@el-var-dribbble: "\f147"; +@el-var-edit: "\f148"; +@el-var-eject: "\f149"; +@el-var-envelope: "\f14b"; +@el-var-envelope-alt: "\f14a"; +@el-var-error: "\f14d"; +@el-var-error-alt: "\f14c"; +@el-var-eur: "\f14e"; +@el-var-exclamation-sign: "\f14f"; +@el-var-eye-close: "\f150"; +@el-var-eye-open: "\f151"; +@el-var-facebook: "\f152"; +@el-var-facetime-video: "\f153"; +@el-var-fast-backward: "\f154"; +@el-var-fast-forward: "\f155"; +@el-var-female: "\f156"; +@el-var-file: "\f15c"; +@el-var-file-alt: "\f157"; +@el-var-file-edit: "\f159"; +@el-var-file-edit-alt: "\f158"; +@el-var-file-new: "\f15b"; +@el-var-file-new-alt: "\f15a"; +@el-var-film: "\f15d"; +@el-var-filter: "\f15e"; +@el-var-fire: "\f15f"; +@el-var-flag: "\f161"; +@el-var-flag-alt: "\f160"; +@el-var-flickr: "\f162"; +@el-var-folder: "\f166"; +@el-var-folder-close: "\f163"; +@el-var-folder-open: "\f164"; +@el-var-folder-sign: "\f165"; +@el-var-font: "\f167"; +@el-var-fontsize: "\f168"; +@el-var-fork: "\f169"; +@el-var-forward: "\f16b"; +@el-var-forward-alt: "\f16a"; +@el-var-foursquare: "\f16c"; +@el-var-friendfeed: "\f16e"; +@el-var-friendfeed-rect: "\f16d"; +@el-var-fullscreen: "\f16f"; +@el-var-gbp: "\f170"; +@el-var-gift: "\f171"; +@el-var-github: "\f173"; +@el-var-github-text: "\f172"; +@el-var-glass: "\f174"; +@el-var-glasses: "\f175"; +@el-var-globe: "\f177"; +@el-var-globe-alt: "\f176"; +@el-var-googleplus: "\f178"; +@el-var-graph: "\f17a"; +@el-var-graph-alt: "\f179"; +@el-var-group: "\f17c"; +@el-var-group-alt: "\f17b"; +@el-var-guidedog: "\f17d"; +@el-var-hand-down: "\f17e"; +@el-var-hand-left: "\f17f"; +@el-var-hand-right: "\f180"; +@el-var-hand-up: "\f181"; +@el-var-hdd: "\f182"; +@el-var-headphones: "\f183"; +@el-var-hearing-impaired: "\f184"; +@el-var-heart: "\f187"; +@el-var-heart-alt: "\f185"; +@el-var-heart-empty: "\f186"; +@el-var-home: "\f189"; +@el-var-home-alt: "\f188"; +@el-var-hourglass: "\f18a"; +@el-var-idea: "\f18c"; +@el-var-idea-alt: "\f18b"; +@el-var-inbox: "\f18f"; +@el-var-inbox-alt: "\f18d"; +@el-var-inbox-box: "\f18e"; +@el-var-indent-left: "\f190"; +@el-var-indent-right: "\f191"; +@el-var-info-circle: "\f192"; +@el-var-instagram: "\f193"; +@el-var-iphone-home: "\f194"; +@el-var-italic: "\f195"; +@el-var-key: "\f196"; +@el-var-laptop: "\f198"; +@el-var-laptop-alt: "\f197"; +@el-var-lastfm: "\f199"; +@el-var-leaf: "\f19a"; +@el-var-lines: "\f19b"; +@el-var-link: "\f19c"; +@el-var-linkedin: "\f19d"; +@el-var-list: "\f19f"; +@el-var-list-alt: "\f19e"; +@el-var-livejournal: "\f1a0"; +@el-var-lock: "\f1a2"; +@el-var-lock-alt: "\f1a1"; +@el-var-magic: "\f1a3"; +@el-var-magnet: "\f1a4"; +@el-var-male: "\f1a5"; +@el-var-map-marker: "\f1a7"; +@el-var-map-marker-alt: "\f1a6"; +@el-var-mic: "\f1a9"; +@el-var-mic-alt: "\f1a8"; +@el-var-minus: "\f1ab"; +@el-var-minus-sign: "\f1aa"; +@el-var-move: "\f1ac"; +@el-var-music: "\f1ad"; +@el-var-myspace: "\f1ae"; +@el-var-network: "\f1af"; +@el-var-off: "\f1b0"; +@el-var-ok: "\f1b3"; +@el-var-ok-circle: "\f1b1"; +@el-var-ok-sign: "\f1b2"; +@el-var-opensource: "\f1b4"; +@el-var-paper-clip: "\f1b6"; +@el-var-paper-clip-alt: "\f1b5"; +@el-var-path: "\f1b7"; +@el-var-pause: "\f1b9"; +@el-var-pause-alt: "\f1b8"; +@el-var-pencil: "\f1bb"; +@el-var-pencil-alt: "\f1ba"; +@el-var-person: "\f1bc"; +@el-var-phone: "\f1be"; +@el-var-phone-alt: "\f1bd"; +@el-var-photo: "\f1c0"; +@el-var-photo-alt: "\f1bf"; +@el-var-picasa: "\f1c1"; +@el-var-picture: "\f1c2"; +@el-var-pinterest: "\f1c3"; +@el-var-plane: "\f1c4"; +@el-var-play: "\f1c7"; +@el-var-play-alt: "\f1c5"; +@el-var-play-circle: "\f1c6"; +@el-var-plurk: "\f1c9"; +@el-var-plurk-alt: "\f1c8"; +@el-var-plus: "\f1cb"; +@el-var-plus-sign: "\f1ca"; +@el-var-podcast: "\f1cc"; +@el-var-print: "\f1cd"; +@el-var-puzzle: "\f1ce"; +@el-var-qrcode: "\f1cf"; +@el-var-question: "\f1d1"; +@el-var-question-sign: "\f1d0"; +@el-var-quote-alt: "\f1d2"; +@el-var-quote-right: "\f1d4"; +@el-var-quote-right-alt: "\f1d3"; +@el-var-quotes: "\f1d5"; +@el-var-random: "\f1d6"; +@el-var-record: "\f1d7"; +@el-var-reddit: "\f1d8"; +@el-var-redux: "\f1d9"; +@el-var-refresh: "\f1da"; +@el-var-remove: "\f1dd"; +@el-var-remove-circle: "\f1db"; +@el-var-remove-sign: "\f1dc"; +@el-var-repeat: "\f1df"; +@el-var-repeat-alt: "\f1de"; +@el-var-resize-full: "\f1e0"; +@el-var-resize-horizontal: "\f1e1"; +@el-var-resize-small: "\f1e2"; +@el-var-resize-vertical: "\f1e3"; +@el-var-return-key: "\f1e4"; +@el-var-retweet: "\f1e5"; +@el-var-reverse-alt: "\f1e6"; +@el-var-road: "\f1e7"; +@el-var-rss: "\f1e8"; +@el-var-scissors: "\f1e9"; +@el-var-screen: "\f1eb"; +@el-var-screen-alt: "\f1ea"; +@el-var-screenshot: "\f1ec"; +@el-var-search: "\f1ee"; +@el-var-search-alt: "\f1ed"; +@el-var-share: "\f1f0"; +@el-var-share-alt: "\f1ef"; +@el-var-shopping-cart: "\f1f2"; +@el-var-shopping-cart-sign: "\f1f1"; +@el-var-signal: "\f1f3"; +@el-var-skype: "\f1f4"; +@el-var-slideshare: "\f1f5"; +@el-var-smiley: "\f1f7"; +@el-var-smiley-alt: "\f1f6"; +@el-var-soundcloud: "\f1f8"; +@el-var-speaker: "\f1f9"; +@el-var-spotify: "\f1fa"; +@el-var-stackoverflow: "\f1fb"; +@el-var-star: "\f1fe"; +@el-var-star-alt: "\f1fc"; +@el-var-star-empty: "\f1fd"; +@el-var-step-backward: "\f1ff"; +@el-var-step-forward: "\f200"; +@el-var-stop: "\f202"; +@el-var-stop-alt: "\f201"; +@el-var-stumbleupon: "\f203"; +@el-var-tag: "\f204"; +@el-var-tags: "\f205"; +@el-var-tasks: "\f206"; +@el-var-text-height: "\f207"; +@el-var-text-width: "\f208"; +@el-var-th: "\f20b"; +@el-var-th-large: "\f209"; +@el-var-th-list: "\f20a"; +@el-var-thumbs-down: "\f20c"; +@el-var-thumbs-up: "\f20d"; +@el-var-time: "\f20f"; +@el-var-time-alt: "\f20e"; +@el-var-tint: "\f210"; +@el-var-torso: "\f211"; +@el-var-trash: "\f213"; +@el-var-trash-alt: "\f212"; +@el-var-tumblr: "\f214"; +@el-var-twitter: "\f215"; +@el-var-universal-access: "\f216"; +@el-var-unlock: "\f218"; +@el-var-unlock-alt: "\f217"; +@el-var-upload: "\f219"; +@el-var-usd: "\f21a"; +@el-var-user: "\f21b"; +@el-var-viadeo: "\f21c"; +@el-var-video: "\f21f"; +@el-var-video-alt: "\f21d"; +@el-var-video-chat: "\f21e"; +@el-var-view-mode: "\f220"; +@el-var-vimeo: "\f221"; +@el-var-vkontakte: "\f222"; +@el-var-volume-down: "\f223"; +@el-var-volume-off: "\f224"; +@el-var-volume-up: "\f225"; +@el-var-w3c: "\f226"; +@el-var-warning-sign: "\f227"; +@el-var-website: "\f229"; +@el-var-website-alt: "\f228"; +@el-var-wheelchair: "\f22a"; +@el-var-wordpress: "\f22b"; +@el-var-wrench: "\f22d"; +@el-var-wrench-alt: "\f22c"; +@el-var-youtube: "\f22e"; +@el-var-zoom-in: "\f22f"; +@el-var-zoom-out: "\f230"; + diff --git a/Public/styles/elusive-icons/scss/_animated.scss b/Public/styles/elusive-icons/scss/_animated.scss new file mode 100644 index 0000000..7661bb0 --- /dev/null +++ b/Public/styles/elusive-icons/scss/_animated.scss @@ -0,0 +1,34 @@ +// Spinning Icons +// -------------------------- + +.#{$el-css-prefix}-spin { + -webkit-animation: el-spin 2s infinite linear; + animation: el-spin 2s infinite linear; +} + +.#{$el-css-prefix}-pulse { + -webkit-animation: el-spin 1s infinite steps(8); + animation: el-spin 1s infinite steps(8); +} + +@-webkit-keyframes el-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} + +@keyframes el-spin { + 0% { + -webkit-transform: rotate(0deg); + transform: rotate(0deg); + } + 100% { + -webkit-transform: rotate(359deg); + transform: rotate(359deg); + } +} diff --git a/Public/styles/elusive-icons/scss/_bordered-pulled.scss b/Public/styles/elusive-icons/scss/_bordered-pulled.scss new file mode 100644 index 0000000..c5a0858 --- /dev/null +++ b/Public/styles/elusive-icons/scss/_bordered-pulled.scss @@ -0,0 +1,16 @@ +// Bordered & Pulled +// ------------------------- + +.#{$el-css-prefix}-border { + padding: .2em .25em .15em; + border: solid .08em $el-border-color; + border-radius: .1em; +} + +.pull-right { float: right; } +.pull-left { float: left; } + +.#{$el-css-prefix} { + &.pull-left { margin-right: .3em; } + &.pull-right { margin-left: .3em; } +} diff --git a/Public/styles/elusive-icons/scss/_core.scss b/Public/styles/elusive-icons/scss/_core.scss new file mode 100644 index 0000000..e987377 --- /dev/null +++ b/Public/styles/elusive-icons/scss/_core.scss @@ -0,0 +1,13 @@ +// Base Class Definition +// ------------------------- + +.#{$el-css-prefix} { + display: inline-block; + font: normal normal normal #{$el-font-size-base}/1 'Elusive-Icons'; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + transform: translate(0, 0); // ensures no half-pixel rendering in firefox + +} diff --git a/Public/styles/elusive-icons/scss/_fixed-width.scss b/Public/styles/elusive-icons/scss/_fixed-width.scss new file mode 100644 index 0000000..0458753 --- /dev/null +++ b/Public/styles/elusive-icons/scss/_fixed-width.scss @@ -0,0 +1,6 @@ +// Fixed Width Icons +// ------------------------- +.#{$el-css-prefix}-fw { + width: (18em / 14); + text-align: center; +} diff --git a/Public/styles/elusive-icons/scss/_icons.scss b/Public/styles/elusive-icons/scss/_icons.scss new file mode 100644 index 0000000..171ba3a --- /dev/null +++ b/Public/styles/elusive-icons/scss/_icons.scss @@ -0,0 +1,307 @@ +/* Elusive Icons uses the Unicode Private Use Area (PUA) to ensure screen + readers do not read off random characters that represent icons */ + +.#{$el-css-prefix}-address-book-alt:before { content: $el-var-address-book-alt; } +.#{$el-css-prefix}-address-book:before { content: $el-var-address-book; } +.#{$el-css-prefix}-adjust-alt:before { content: $el-var-adjust-alt; } +.#{$el-css-prefix}-adjust:before { content: $el-var-adjust; } +.#{$el-css-prefix}-adult:before { content: $el-var-adult; } +.#{$el-css-prefix}-align-center:before { content: $el-var-align-center; } +.#{$el-css-prefix}-align-justify:before { content: $el-var-align-justify; } +.#{$el-css-prefix}-align-left:before { content: $el-var-align-left; } +.#{$el-css-prefix}-align-right:before { content: $el-var-align-right; } +.#{$el-css-prefix}-arrow-down:before { content: $el-var-arrow-down; } +.#{$el-css-prefix}-arrow-left:before { content: $el-var-arrow-left; } +.#{$el-css-prefix}-arrow-right:before { content: $el-var-arrow-right; } +.#{$el-css-prefix}-arrow-up:before { content: $el-var-arrow-up; } +.#{$el-css-prefix}-asl:before { content: $el-var-asl; } +.#{$el-css-prefix}-asterisk:before { content: $el-var-asterisk; } +.#{$el-css-prefix}-backward:before { content: $el-var-backward; } +.#{$el-css-prefix}-ban-circle:before { content: $el-var-ban-circle; } +.#{$el-css-prefix}-barcode:before { content: $el-var-barcode; } +.#{$el-css-prefix}-behance:before { content: $el-var-behance; } +.#{$el-css-prefix}-bell:before { content: $el-var-bell; } +.#{$el-css-prefix}-blind:before { content: $el-var-blind; } +.#{$el-css-prefix}-blogger:before { content: $el-var-blogger; } +.#{$el-css-prefix}-bold:before { content: $el-var-bold; } +.#{$el-css-prefix}-book:before { content: $el-var-book; } +.#{$el-css-prefix}-bookmark-empty:before { content: $el-var-bookmark-empty; } +.#{$el-css-prefix}-bookmark:before { content: $el-var-bookmark; } +.#{$el-css-prefix}-braille:before { content: $el-var-braille; } +.#{$el-css-prefix}-briefcase:before { content: $el-var-briefcase; } +.#{$el-css-prefix}-broom:before { content: $el-var-broom; } +.#{$el-css-prefix}-brush:before { content: $el-var-brush; } +.#{$el-css-prefix}-bulb:before { content: $el-var-bulb; } +.#{$el-css-prefix}-bullhorn:before { content: $el-var-bullhorn; } +.#{$el-css-prefix}-calendar-sign:before { content: $el-var-calendar-sign; } +.#{$el-css-prefix}-calendar:before { content: $el-var-calendar; } +.#{$el-css-prefix}-camera:before { content: $el-var-camera; } +.#{$el-css-prefix}-car:before { content: $el-var-car; } +.#{$el-css-prefix}-caret-down:before { content: $el-var-caret-down; } +.#{$el-css-prefix}-caret-left:before { content: $el-var-caret-left; } +.#{$el-css-prefix}-caret-right:before { content: $el-var-caret-right; } +.#{$el-css-prefix}-caret-up:before { content: $el-var-caret-up; } +.#{$el-css-prefix}-cc:before { content: $el-var-cc; } +.#{$el-css-prefix}-certificate:before { content: $el-var-certificate; } +.#{$el-css-prefix}-check-empty:before { content: $el-var-check-empty; } +.#{$el-css-prefix}-check:before { content: $el-var-check; } +.#{$el-css-prefix}-chevron-down:before { content: $el-var-chevron-down; } +.#{$el-css-prefix}-chevron-left:before { content: $el-var-chevron-left; } +.#{$el-css-prefix}-chevron-right:before { content: $el-var-chevron-right; } +.#{$el-css-prefix}-chevron-up:before { content: $el-var-chevron-up; } +.#{$el-css-prefix}-child:before { content: $el-var-child; } +.#{$el-css-prefix}-circle-arrow-down:before { content: $el-var-circle-arrow-down; } +.#{$el-css-prefix}-circle-arrow-left:before { content: $el-var-circle-arrow-left; } +.#{$el-css-prefix}-circle-arrow-right:before { content: $el-var-circle-arrow-right; } +.#{$el-css-prefix}-circle-arrow-up:before { content: $el-var-circle-arrow-up; } +.#{$el-css-prefix}-cloud-alt:before { content: $el-var-cloud-alt; } +.#{$el-css-prefix}-cloud:before { content: $el-var-cloud; } +.#{$el-css-prefix}-cog-alt:before { content: $el-var-cog-alt; } +.#{$el-css-prefix}-cog:before { content: $el-var-cog; } +.#{$el-css-prefix}-cogs:before { content: $el-var-cogs; } +.#{$el-css-prefix}-comment-alt:before { content: $el-var-comment-alt; } +.#{$el-css-prefix}-comment:before { content: $el-var-comment; } +.#{$el-css-prefix}-compass-alt:before { content: $el-var-compass-alt; } +.#{$el-css-prefix}-compass:before { content: $el-var-compass; } +.#{$el-css-prefix}-credit-card:before { content: $el-var-credit-card; } +.#{$el-css-prefix}-css:before { content: $el-var-css; } +.#{$el-css-prefix}-dashboard:before { content: $el-var-dashboard; } +.#{$el-css-prefix}-delicious:before { content: $el-var-delicious; } +.#{$el-css-prefix}-deviantart:before { content: $el-var-deviantart; } +.#{$el-css-prefix}-digg:before { content: $el-var-digg; } +.#{$el-css-prefix}-download-alt:before { content: $el-var-download-alt; } +.#{$el-css-prefix}-download:before { content: $el-var-download; } +.#{$el-css-prefix}-dribbble:before { content: $el-var-dribbble; } +.#{$el-css-prefix}-edit:before { content: $el-var-edit; } +.#{$el-css-prefix}-eject:before { content: $el-var-eject; } +.#{$el-css-prefix}-envelope-alt:before { content: $el-var-envelope-alt; } +.#{$el-css-prefix}-envelope:before { content: $el-var-envelope; } +.#{$el-css-prefix}-error-alt:before { content: $el-var-error-alt; } +.#{$el-css-prefix}-error:before { content: $el-var-error; } +.#{$el-css-prefix}-eur:before { content: $el-var-eur; } +.#{$el-css-prefix}-exclamation-sign:before { content: $el-var-exclamation-sign; } +.#{$el-css-prefix}-eye-close:before { content: $el-var-eye-close; } +.#{$el-css-prefix}-eye-open:before { content: $el-var-eye-open; } +.#{$el-css-prefix}-facebook:before { content: $el-var-facebook; } +.#{$el-css-prefix}-facetime-video:before { content: $el-var-facetime-video; } +.#{$el-css-prefix}-fast-backward:before { content: $el-var-fast-backward; } +.#{$el-css-prefix}-fast-forward:before { content: $el-var-fast-forward; } +.#{$el-css-prefix}-female:before { content: $el-var-female; } +.#{$el-css-prefix}-file-alt:before { content: $el-var-file-alt; } +.#{$el-css-prefix}-file-edit-alt:before { content: $el-var-file-edit-alt; } +.#{$el-css-prefix}-file-edit:before { content: $el-var-file-edit; } +.#{$el-css-prefix}-file-new-alt:before { content: $el-var-file-new-alt; } +.#{$el-css-prefix}-file-new:before { content: $el-var-file-new; } +.#{$el-css-prefix}-file:before { content: $el-var-file; } +.#{$el-css-prefix}-film:before { content: $el-var-film; } +.#{$el-css-prefix}-filter:before { content: $el-var-filter; } +.#{$el-css-prefix}-fire:before { content: $el-var-fire; } +.#{$el-css-prefix}-flag-alt:before { content: $el-var-flag-alt; } +.#{$el-css-prefix}-flag:before { content: $el-var-flag; } +.#{$el-css-prefix}-flickr:before { content: $el-var-flickr; } +.#{$el-css-prefix}-folder-close:before { content: $el-var-folder-close; } +.#{$el-css-prefix}-folder-open:before { content: $el-var-folder-open; } +.#{$el-css-prefix}-folder-sign:before { content: $el-var-folder-sign; } +.#{$el-css-prefix}-folder:before { content: $el-var-folder; } +.#{$el-css-prefix}-font:before { content: $el-var-font; } +.#{$el-css-prefix}-fontsize:before { content: $el-var-fontsize; } +.#{$el-css-prefix}-fork:before { content: $el-var-fork; } +.#{$el-css-prefix}-forward-alt:before { content: $el-var-forward-alt; } +.#{$el-css-prefix}-forward:before { content: $el-var-forward; } +.#{$el-css-prefix}-foursquare:before { content: $el-var-foursquare; } +.#{$el-css-prefix}-friendfeed-rect:before { content: $el-var-friendfeed-rect; } +.#{$el-css-prefix}-friendfeed:before { content: $el-var-friendfeed; } +.#{$el-css-prefix}-fullscreen:before { content: $el-var-fullscreen; } +.#{$el-css-prefix}-gbp:before { content: $el-var-gbp; } +.#{$el-css-prefix}-gift:before { content: $el-var-gift; } +.#{$el-css-prefix}-github-text:before { content: $el-var-github-text; } +.#{$el-css-prefix}-github:before { content: $el-var-github; } +.#{$el-css-prefix}-glass:before { content: $el-var-glass; } +.#{$el-css-prefix}-glasses:before { content: $el-var-glasses; } +.#{$el-css-prefix}-globe-alt:before { content: $el-var-globe-alt; } +.#{$el-css-prefix}-globe:before { content: $el-var-globe; } +.#{$el-css-prefix}-googleplus:before { content: $el-var-googleplus; } +.#{$el-css-prefix}-graph-alt:before { content: $el-var-graph-alt; } +.#{$el-css-prefix}-graph:before { content: $el-var-graph; } +.#{$el-css-prefix}-group-alt:before { content: $el-var-group-alt; } +.#{$el-css-prefix}-group:before { content: $el-var-group; } +.#{$el-css-prefix}-guidedog:before { content: $el-var-guidedog; } +.#{$el-css-prefix}-hand-down:before { content: $el-var-hand-down; } +.#{$el-css-prefix}-hand-left:before { content: $el-var-hand-left; } +.#{$el-css-prefix}-hand-right:before { content: $el-var-hand-right; } +.#{$el-css-prefix}-hand-up:before { content: $el-var-hand-up; } +.#{$el-css-prefix}-hdd:before { content: $el-var-hdd; } +.#{$el-css-prefix}-headphones:before { content: $el-var-headphones; } +.#{$el-css-prefix}-hearing-impaired:before { content: $el-var-hearing-impaired; } +.#{$el-css-prefix}-heart-alt:before { content: $el-var-heart-alt; } +.#{$el-css-prefix}-heart-empty:before { content: $el-var-heart-empty; } +.#{$el-css-prefix}-heart:before { content: $el-var-heart; } +.#{$el-css-prefix}-home-alt:before { content: $el-var-home-alt; } +.#{$el-css-prefix}-home:before { content: $el-var-home; } +.#{$el-css-prefix}-hourglass:before { content: $el-var-hourglass; } +.#{$el-css-prefix}-idea-alt:before { content: $el-var-idea-alt; } +.#{$el-css-prefix}-idea:before { content: $el-var-idea; } +.#{$el-css-prefix}-inbox-alt:before { content: $el-var-inbox-alt; } +.#{$el-css-prefix}-inbox-box:before { content: $el-var-inbox-box; } +.#{$el-css-prefix}-inbox:before { content: $el-var-inbox; } +.#{$el-css-prefix}-indent-left:before { content: $el-var-indent-left; } +.#{$el-css-prefix}-indent-right:before { content: $el-var-indent-right; } +.#{$el-css-prefix}-info-circle:before { content: $el-var-info-circle; } +.#{$el-css-prefix}-instagram:before { content: $el-var-instagram; } +.#{$el-css-prefix}-iphone-home:before { content: $el-var-iphone-home; } +.#{$el-css-prefix}-italic:before { content: $el-var-italic; } +.#{$el-css-prefix}-key:before { content: $el-var-key; } +.#{$el-css-prefix}-laptop-alt:before { content: $el-var-laptop-alt; } +.#{$el-css-prefix}-laptop:before { content: $el-var-laptop; } +.#{$el-css-prefix}-lastfm:before { content: $el-var-lastfm; } +.#{$el-css-prefix}-leaf:before { content: $el-var-leaf; } +.#{$el-css-prefix}-lines:before { content: $el-var-lines; } +.#{$el-css-prefix}-link:before { content: $el-var-link; } +.#{$el-css-prefix}-linkedin:before { content: $el-var-linkedin; } +.#{$el-css-prefix}-list-alt:before { content: $el-var-list-alt; } +.#{$el-css-prefix}-list:before { content: $el-var-list; } +.#{$el-css-prefix}-livejournal:before { content: $el-var-livejournal; } +.#{$el-css-prefix}-lock-alt:before { content: $el-var-lock-alt; } +.#{$el-css-prefix}-lock:before { content: $el-var-lock; } +.#{$el-css-prefix}-magic:before { content: $el-var-magic; } +.#{$el-css-prefix}-magnet:before { content: $el-var-magnet; } +.#{$el-css-prefix}-male:before { content: $el-var-male; } +.#{$el-css-prefix}-map-marker-alt:before { content: $el-var-map-marker-alt; } +.#{$el-css-prefix}-map-marker:before { content: $el-var-map-marker; } +.#{$el-css-prefix}-mic-alt:before { content: $el-var-mic-alt; } +.#{$el-css-prefix}-mic:before { content: $el-var-mic; } +.#{$el-css-prefix}-minus-sign:before { content: $el-var-minus-sign; } +.#{$el-css-prefix}-minus:before { content: $el-var-minus; } +.#{$el-css-prefix}-move:before { content: $el-var-move; } +.#{$el-css-prefix}-music:before { content: $el-var-music; } +.#{$el-css-prefix}-myspace:before { content: $el-var-myspace; } +.#{$el-css-prefix}-network:before { content: $el-var-network; } +.#{$el-css-prefix}-off:before { content: $el-var-off; } +.#{$el-css-prefix}-ok-circle:before { content: $el-var-ok-circle; } +.#{$el-css-prefix}-ok-sign:before { content: $el-var-ok-sign; } +.#{$el-css-prefix}-ok:before { content: $el-var-ok; } +.#{$el-css-prefix}-opensource:before { content: $el-var-opensource; } +.#{$el-css-prefix}-paper-clip-alt:before { content: $el-var-paper-clip-alt; } +.#{$el-css-prefix}-paper-clip:before { content: $el-var-paper-clip; } +.#{$el-css-prefix}-path:before { content: $el-var-path; } +.#{$el-css-prefix}-pause-alt:before { content: $el-var-pause-alt; } +.#{$el-css-prefix}-pause:before { content: $el-var-pause; } +.#{$el-css-prefix}-pencil-alt:before { content: $el-var-pencil-alt; } +.#{$el-css-prefix}-pencil:before { content: $el-var-pencil; } +.#{$el-css-prefix}-person:before { content: $el-var-person; } +.#{$el-css-prefix}-phone-alt:before { content: $el-var-phone-alt; } +.#{$el-css-prefix}-phone:before { content: $el-var-phone; } +.#{$el-css-prefix}-photo-alt:before { content: $el-var-photo-alt; } +.#{$el-css-prefix}-photo:before { content: $el-var-photo; } +.#{$el-css-prefix}-picasa:before { content: $el-var-picasa; } +.#{$el-css-prefix}-picture:before { content: $el-var-picture; } +.#{$el-css-prefix}-pinterest:before { content: $el-var-pinterest; } +.#{$el-css-prefix}-plane:before { content: $el-var-plane; } +.#{$el-css-prefix}-play-alt:before { content: $el-var-play-alt; } +.#{$el-css-prefix}-play-circle:before { content: $el-var-play-circle; } +.#{$el-css-prefix}-play:before { content: $el-var-play; } +.#{$el-css-prefix}-plurk-alt:before { content: $el-var-plurk-alt; } +.#{$el-css-prefix}-plurk:before { content: $el-var-plurk; } +.#{$el-css-prefix}-plus-sign:before { content: $el-var-plus-sign; } +.#{$el-css-prefix}-plus:before { content: $el-var-plus; } +.#{$el-css-prefix}-podcast:before { content: $el-var-podcast; } +.#{$el-css-prefix}-print:before { content: $el-var-print; } +.#{$el-css-prefix}-puzzle:before { content: $el-var-puzzle; } +.#{$el-css-prefix}-qrcode:before { content: $el-var-qrcode; } +.#{$el-css-prefix}-question-sign:before { content: $el-var-question-sign; } +.#{$el-css-prefix}-question:before { content: $el-var-question; } +.#{$el-css-prefix}-quote-alt:before { content: $el-var-quote-alt; } +.#{$el-css-prefix}-quote-right-alt:before { content: $el-var-quote-right-alt; } +.#{$el-css-prefix}-quote-right:before { content: $el-var-quote-right; } +.#{$el-css-prefix}-quotes:before { content: $el-var-quotes; } +.#{$el-css-prefix}-random:before { content: $el-var-random; } +.#{$el-css-prefix}-record:before { content: $el-var-record; } +.#{$el-css-prefix}-reddit:before { content: $el-var-reddit; } +.#{$el-css-prefix}-redux:before { content: $el-var-redux; } +.#{$el-css-prefix}-refresh:before { content: $el-var-refresh; } +.#{$el-css-prefix}-remove-circle:before { content: $el-var-remove-circle; } +.#{$el-css-prefix}-remove-sign:before { content: $el-var-remove-sign; } +.#{$el-css-prefix}-remove:before { content: $el-var-remove; } +.#{$el-css-prefix}-repeat-alt:before { content: $el-var-repeat-alt; } +.#{$el-css-prefix}-repeat:before { content: $el-var-repeat; } +.#{$el-css-prefix}-resize-full:before { content: $el-var-resize-full; } +.#{$el-css-prefix}-resize-horizontal:before { content: $el-var-resize-horizontal; } +.#{$el-css-prefix}-resize-small:before { content: $el-var-resize-small; } +.#{$el-css-prefix}-resize-vertical:before { content: $el-var-resize-vertical; } +.#{$el-css-prefix}-return-key:before { content: $el-var-return-key; } +.#{$el-css-prefix}-retweet:before { content: $el-var-retweet; } +.#{$el-css-prefix}-reverse-alt:before { content: $el-var-reverse-alt; } +.#{$el-css-prefix}-road:before { content: $el-var-road; } +.#{$el-css-prefix}-rss:before { content: $el-var-rss; } +.#{$el-css-prefix}-scissors:before { content: $el-var-scissors; } +.#{$el-css-prefix}-screen-alt:before { content: $el-var-screen-alt; } +.#{$el-css-prefix}-screen:before { content: $el-var-screen; } +.#{$el-css-prefix}-screenshot:before { content: $el-var-screenshot; } +.#{$el-css-prefix}-search-alt:before { content: $el-var-search-alt; } +.#{$el-css-prefix}-search:before { content: $el-var-search; } +.#{$el-css-prefix}-share-alt:before { content: $el-var-share-alt; } +.#{$el-css-prefix}-share:before { content: $el-var-share; } +.#{$el-css-prefix}-shopping-cart-sign:before { content: $el-var-shopping-cart-sign; } +.#{$el-css-prefix}-shopping-cart:before { content: $el-var-shopping-cart; } +.#{$el-css-prefix}-signal:before { content: $el-var-signal; } +.#{$el-css-prefix}-skype:before { content: $el-var-skype; } +.#{$el-css-prefix}-slideshare:before { content: $el-var-slideshare; } +.#{$el-css-prefix}-smiley-alt:before { content: $el-var-smiley-alt; } +.#{$el-css-prefix}-smiley:before { content: $el-var-smiley; } +.#{$el-css-prefix}-soundcloud:before { content: $el-var-soundcloud; } +.#{$el-css-prefix}-speaker:before { content: $el-var-speaker; } +.#{$el-css-prefix}-spotify:before { content: $el-var-spotify; } +.#{$el-css-prefix}-stackoverflow:before { content: $el-var-stackoverflow; } +.#{$el-css-prefix}-star-alt:before { content: $el-var-star-alt; } +.#{$el-css-prefix}-star-empty:before { content: $el-var-star-empty; } +.#{$el-css-prefix}-star:before { content: $el-var-star; } +.#{$el-css-prefix}-step-backward:before { content: $el-var-step-backward; } +.#{$el-css-prefix}-step-forward:before { content: $el-var-step-forward; } +.#{$el-css-prefix}-stop-alt:before { content: $el-var-stop-alt; } +.#{$el-css-prefix}-stop:before { content: $el-var-stop; } +.#{$el-css-prefix}-stumbleupon:before { content: $el-var-stumbleupon; } +.#{$el-css-prefix}-tag:before { content: $el-var-tag; } +.#{$el-css-prefix}-tags:before { content: $el-var-tags; } +.#{$el-css-prefix}-tasks:before { content: $el-var-tasks; } +.#{$el-css-prefix}-text-height:before { content: $el-var-text-height; } +.#{$el-css-prefix}-text-width:before { content: $el-var-text-width; } +.#{$el-css-prefix}-th-large:before { content: $el-var-th-large; } +.#{$el-css-prefix}-th-list:before { content: $el-var-th-list; } +.#{$el-css-prefix}-th:before { content: $el-var-th; } +.#{$el-css-prefix}-thumbs-down:before { content: $el-var-thumbs-down; } +.#{$el-css-prefix}-thumbs-up:before { content: $el-var-thumbs-up; } +.#{$el-css-prefix}-time-alt:before { content: $el-var-time-alt; } +.#{$el-css-prefix}-time:before { content: $el-var-time; } +.#{$el-css-prefix}-tint:before { content: $el-var-tint; } +.#{$el-css-prefix}-torso:before { content: $el-var-torso; } +.#{$el-css-prefix}-trash-alt:before { content: $el-var-trash-alt; } +.#{$el-css-prefix}-trash:before { content: $el-var-trash; } +.#{$el-css-prefix}-tumblr:before { content: $el-var-tumblr; } +.#{$el-css-prefix}-twitter:before { content: $el-var-twitter; } +.#{$el-css-prefix}-universal-access:before { content: $el-var-universal-access; } +.#{$el-css-prefix}-unlock-alt:before { content: $el-var-unlock-alt; } +.#{$el-css-prefix}-unlock:before { content: $el-var-unlock; } +.#{$el-css-prefix}-upload:before { content: $el-var-upload; } +.#{$el-css-prefix}-usd:before { content: $el-var-usd; } +.#{$el-css-prefix}-user:before { content: $el-var-user; } +.#{$el-css-prefix}-viadeo:before { content: $el-var-viadeo; } +.#{$el-css-prefix}-video-alt:before { content: $el-var-video-alt; } +.#{$el-css-prefix}-video-chat:before { content: $el-var-video-chat; } +.#{$el-css-prefix}-video:before { content: $el-var-video; } +.#{$el-css-prefix}-view-mode:before { content: $el-var-view-mode; } +.#{$el-css-prefix}-vimeo:before { content: $el-var-vimeo; } +.#{$el-css-prefix}-vkontakte:before { content: $el-var-vkontakte; } +.#{$el-css-prefix}-volume-down:before { content: $el-var-volume-down; } +.#{$el-css-prefix}-volume-off:before { content: $el-var-volume-off; } +.#{$el-css-prefix}-volume-up:before { content: $el-var-volume-up; } +.#{$el-css-prefix}-w3c:before { content: $el-var-w3c; } +.#{$el-css-prefix}-warning-sign:before { content: $el-var-warning-sign; } +.#{$el-css-prefix}-website-alt:before { content: $el-var-website-alt; } +.#{$el-css-prefix}-website:before { content: $el-var-website; } +.#{$el-css-prefix}-wheelchair:before { content: $el-var-wheelchair; } +.#{$el-css-prefix}-wordpress:before { content: $el-var-wordpress; } +.#{$el-css-prefix}-wrench-alt:before { content: $el-var-wrench-alt; } +.#{$el-css-prefix}-wrench:before { content: $el-var-wrench; } +.#{$el-css-prefix}-youtube:before { content: $el-var-youtube; } +.#{$el-css-prefix}-zoom-in:before { content: $el-var-zoom-in; } +.#{$el-css-prefix}-zoom-out:before { content: $el-var-zoom-out; } diff --git a/Public/styles/elusive-icons/scss/_larger.scss b/Public/styles/elusive-icons/scss/_larger.scss new file mode 100644 index 0000000..f76eab7 --- /dev/null +++ b/Public/styles/elusive-icons/scss/_larger.scss @@ -0,0 +1,13 @@ +// Icon Sizes +// ------------------------- + +/* makes the font 33% larger relative to the icon container */ +.#{$el-css-prefix}-lg { + font-size: (4em / 3); + line-height: (3em / 4); + vertical-align: -15%; +} +.#{$el-css-prefix}-2x { font-size: 2em; } +.#{$el-css-prefix}-3x { font-size: 3em; } +.#{$el-css-prefix}-4x { font-size: 4em; } +.#{$el-css-prefix}-5x { font-size: 5em; } diff --git a/Public/styles/elusive-icons/scss/_list.scss b/Public/styles/elusive-icons/scss/_list.scss new file mode 100644 index 0000000..fefec82 --- /dev/null +++ b/Public/styles/elusive-icons/scss/_list.scss @@ -0,0 +1,19 @@ +// List Icons +// ------------------------- + +.#{$el-css-prefix}-ul { + padding-left: 0; + margin-left: $el-li-width; + list-style-type: none; + > li { position: relative; } +} +.#{$el-css-prefix}-li { + position: absolute; + left: -$el-li-width; + width: $el-li-width; + top: (2em / 14); + text-align: center; + &.#{$el-css-prefix}-lg { + left: -$el-li-width + (4em / 14); + } +} diff --git a/Public/styles/elusive-icons/scss/_mixins.scss b/Public/styles/elusive-icons/scss/_mixins.scss new file mode 100644 index 0000000..b443823 --- /dev/null +++ b/Public/styles/elusive-icons/scss/_mixins.scss @@ -0,0 +1,27 @@ +// Mixins +// -------------------------- + +@mixin el-icon() { + display: inline-block; + font: normal normal normal #{$el-font-size-base}/1 'Elusive-Icons'; // shortening font declaration + font-size: inherit; // can't have font-size inherit on line above, so need to override + text-rendering: auto; // optimizelegibility throws things off #1094 + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; + transform: translate(0, 0); // ensures no half-pixel rendering in firefox + +} + +@mixin el-icon-rotate($degrees, $rotation) { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); + -webkit-transform: rotate($degrees); + -ms-transform: rotate($degrees); + transform: rotate($degrees); +} + +@mixin el-icon-flip($horiz, $vert, $rotation) { + filter: progid:DXImageTransform.Microsoft.BasicImage(rotation=#{$rotation}); + -webkit-transform: scale($horiz, $vert); + -ms-transform: scale($horiz, $vert); + transform: scale($horiz, $vert); +} diff --git a/Public/styles/elusive-icons/scss/_path.scss b/Public/styles/elusive-icons/scss/_path.scss new file mode 100644 index 0000000..4989ef2 --- /dev/null +++ b/Public/styles/elusive-icons/scss/_path.scss @@ -0,0 +1,14 @@ +/* FONT PATH + * -------------------------- */ + +@font-face { + font-family: 'Elusive-Icons'; + src: url('#{$el-font-path}/elusiveicons-webfont.eot?v=#{$el-version}'); + src: url('#{$el-font-path}/elusiveicons-webfont.eot?#iefix&v=#{$el-version}') format('embedded-opentype'), + //url('#{$el-font-path}/elusiveicons-webfont.woff2?v=#{$el-version}') format('woff2'), + url('#{$el-font-path}/elusiveicons-webfont.woff?v=#{$el-version}') format('woff'), + url('#{$el-font-path}/elusiveicons-webfont.ttf?v=#{$el-version}') format('truetype'), + url('#{$el-font-path}/elusiveicons-webfont.svg?v=#{$el-version}#elusiveiconsregular') format('svg'); + font-weight: normal; + font-style: normal; +} diff --git a/Public/styles/elusive-icons/scss/_rotated-flipped.scss b/Public/styles/elusive-icons/scss/_rotated-flipped.scss new file mode 100644 index 0000000..8248161 --- /dev/null +++ b/Public/styles/elusive-icons/scss/_rotated-flipped.scss @@ -0,0 +1,20 @@ +// Rotated & Flipped Icons +// ------------------------- + +.#{$el-css-prefix}-rotate-90 { @include el-icon-rotate(90deg, 1); } +.#{$el-css-prefix}-rotate-180 { @include el-icon-rotate(180deg, 2); } +.#{$el-css-prefix}-rotate-270 { @include el-icon-rotate(270deg, 3); } + +.#{$el-css-prefix}-flip-horizontal { @include el-icon-flip(-1, 1, 0); } +.#{$el-css-prefix}-flip-vertical { @include el-icon-flip(1, -1, 2); } + +// Hook for IE8-9 +// ------------------------- + +:root .#{$el-css-prefix}-rotate-90, +:root .#{$el-css-prefix}-rotate-180, +:root .#{$el-css-prefix}-rotate-270, +:root .#{$el-css-prefix}-flip-horizontal, +:root .#{$el-css-prefix}-flip-vertical { + filter: none; +} diff --git a/Public/styles/elusive-icons/scss/_stacked.scss b/Public/styles/elusive-icons/scss/_stacked.scss new file mode 100644 index 0000000..68d84bb --- /dev/null +++ b/Public/styles/elusive-icons/scss/_stacked.scss @@ -0,0 +1,20 @@ +// Stacked Icons +// ------------------------- + +.#{$el-css-prefix}-stack { + position: relative; + display: inline-block; + width: 2em; + height: 2em; + line-height: 2em; + vertical-align: middle; +} +.#{$el-css-prefix}-stack-1x, .#{$el-css-prefix}-stack-2x { + position: absolute; + left: 0; + width: 100%; + text-align: center; +} +.#{$el-css-prefix}-stack-1x { line-height: inherit; } +.#{$el-css-prefix}-stack-2x { font-size: 2em; } +.#{$el-css-prefix}-inverse { color: $el-inverse; } diff --git a/Public/styles/elusive-icons/scss/_variables.scss b/Public/styles/elusive-icons/scss/_variables.scss new file mode 100644 index 0000000..30a0aec --- /dev/null +++ b/Public/styles/elusive-icons/scss/_variables.scss @@ -0,0 +1,317 @@ +// Variables +// -------------------------- + +$el-font-path: "../fonts" !default; +$el-font-size-base: 14px !default; +//$el-font-path: "//netdna.bootstrapcdn.com/elusive-icons/2.0.0/fonts" !default; // for referencing Bootstrap CDN font files directly +$el-css-prefix: el !default; +$el-version: "2.0.0" !default; +$el-border-color: #eee !default; +$el-inverse: #fff !default; +$el-li-width: (30em / 14) !default; + +$el-var-address-book: "\f102"; +$el-var-address-book-alt: "\f101"; +$el-var-adjust: "\f104"; +$el-var-adjust-alt: "\f103"; +$el-var-adult: "\f105"; +$el-var-align-center: "\f106"; +$el-var-align-justify: "\f107"; +$el-var-align-left: "\f108"; +$el-var-align-right: "\f109"; +$el-var-arrow-down: "\f10a"; +$el-var-arrow-left: "\f10b"; +$el-var-arrow-right: "\f10c"; +$el-var-arrow-up: "\f10d"; +$el-var-asl: "\f10e"; +$el-var-asterisk: "\f10f"; +$el-var-backward: "\f110"; +$el-var-ban-circle: "\f111"; +$el-var-barcode: "\f112"; +$el-var-behance: "\f113"; +$el-var-bell: "\f114"; +$el-var-blind: "\f115"; +$el-var-blogger: "\f116"; +$el-var-bold: "\f117"; +$el-var-book: "\f118"; +$el-var-bookmark: "\f11a"; +$el-var-bookmark-empty: "\f119"; +$el-var-braille: "\f11b"; +$el-var-briefcase: "\f11c"; +$el-var-broom: "\f11d"; +$el-var-brush: "\f11e"; +$el-var-bulb: "\f11f"; +$el-var-bullhorn: "\f120"; +$el-var-calendar: "\f122"; +$el-var-calendar-sign: "\f121"; +$el-var-camera: "\f123"; +$el-var-car: "\f124"; +$el-var-caret-down: "\f125"; +$el-var-caret-left: "\f126"; +$el-var-caret-right: "\f127"; +$el-var-caret-up: "\f128"; +$el-var-cc: "\f129"; +$el-var-certificate: "\f12a"; +$el-var-check: "\f12c"; +$el-var-check-empty: "\f12b"; +$el-var-chevron-down: "\f12d"; +$el-var-chevron-left: "\f12e"; +$el-var-chevron-right: "\f12f"; +$el-var-chevron-up: "\f130"; +$el-var-child: "\f131"; +$el-var-circle-arrow-down: "\f132"; +$el-var-circle-arrow-left: "\f133"; +$el-var-circle-arrow-right: "\f134"; +$el-var-circle-arrow-up: "\f135"; +$el-var-cloud: "\f137"; +$el-var-cloud-alt: "\f136"; +$el-var-cog: "\f139"; +$el-var-cog-alt: "\f138"; +$el-var-cogs: "\f13a"; +$el-var-comment: "\f13c"; +$el-var-comment-alt: "\f13b"; +$el-var-compass: "\f13e"; +$el-var-compass-alt: "\f13d"; +$el-var-credit-card: "\f13f"; +$el-var-css: "\f140"; +$el-var-dashboard: "\f141"; +$el-var-delicious: "\f142"; +$el-var-deviantart: "\f143"; +$el-var-digg: "\f144"; +$el-var-download: "\f146"; +$el-var-download-alt: "\f145"; +$el-var-dribbble: "\f147"; +$el-var-edit: "\f148"; +$el-var-eject: "\f149"; +$el-var-envelope: "\f14b"; +$el-var-envelope-alt: "\f14a"; +$el-var-error: "\f14d"; +$el-var-error-alt: "\f14c"; +$el-var-eur: "\f14e"; +$el-var-exclamation-sign: "\f14f"; +$el-var-eye-close: "\f150"; +$el-var-eye-open: "\f151"; +$el-var-facebook: "\f152"; +$el-var-facetime-video: "\f153"; +$el-var-fast-backward: "\f154"; +$el-var-fast-forward: "\f155"; +$el-var-female: "\f156"; +$el-var-file: "\f15c"; +$el-var-file-alt: "\f157"; +$el-var-file-edit: "\f159"; +$el-var-file-edit-alt: "\f158"; +$el-var-file-new: "\f15b"; +$el-var-file-new-alt: "\f15a"; +$el-var-film: "\f15d"; +$el-var-filter: "\f15e"; +$el-var-fire: "\f15f"; +$el-var-flag: "\f161"; +$el-var-flag-alt: "\f160"; +$el-var-flickr: "\f162"; +$el-var-folder: "\f166"; +$el-var-folder-close: "\f163"; +$el-var-folder-open: "\f164"; +$el-var-folder-sign: "\f165"; +$el-var-font: "\f167"; +$el-var-fontsize: "\f168"; +$el-var-fork: "\f169"; +$el-var-forward: "\f16b"; +$el-var-forward-alt: "\f16a"; +$el-var-foursquare: "\f16c"; +$el-var-friendfeed: "\f16e"; +$el-var-friendfeed-rect: "\f16d"; +$el-var-fullscreen: "\f16f"; +$el-var-gbp: "\f170"; +$el-var-gift: "\f171"; +$el-var-github: "\f173"; +$el-var-github-text: "\f172"; +$el-var-glass: "\f174"; +$el-var-glasses: "\f175"; +$el-var-globe: "\f177"; +$el-var-globe-alt: "\f176"; +$el-var-googleplus: "\f178"; +$el-var-graph: "\f17a"; +$el-var-graph-alt: "\f179"; +$el-var-group: "\f17c"; +$el-var-group-alt: "\f17b"; +$el-var-guidedog: "\f17d"; +$el-var-hand-down: "\f17e"; +$el-var-hand-left: "\f17f"; +$el-var-hand-right: "\f180"; +$el-var-hand-up: "\f181"; +$el-var-hdd: "\f182"; +$el-var-headphones: "\f183"; +$el-var-hearing-impaired: "\f184"; +$el-var-heart: "\f187"; +$el-var-heart-alt: "\f185"; +$el-var-heart-empty: "\f186"; +$el-var-home: "\f189"; +$el-var-home-alt: "\f188"; +$el-var-hourglass: "\f18a"; +$el-var-idea: "\f18c"; +$el-var-idea-alt: "\f18b"; +$el-var-inbox: "\f18f"; +$el-var-inbox-alt: "\f18d"; +$el-var-inbox-box: "\f18e"; +$el-var-indent-left: "\f190"; +$el-var-indent-right: "\f191"; +$el-var-info-circle: "\f192"; +$el-var-instagram: "\f193"; +$el-var-iphone-home: "\f194"; +$el-var-italic: "\f195"; +$el-var-key: "\f196"; +$el-var-laptop: "\f198"; +$el-var-laptop-alt: "\f197"; +$el-var-lastfm: "\f199"; +$el-var-leaf: "\f19a"; +$el-var-lines: "\f19b"; +$el-var-link: "\f19c"; +$el-var-linkedin: "\f19d"; +$el-var-list: "\f19f"; +$el-var-list-alt: "\f19e"; +$el-var-livejournal: "\f1a0"; +$el-var-lock: "\f1a2"; +$el-var-lock-alt: "\f1a1"; +$el-var-magic: "\f1a3"; +$el-var-magnet: "\f1a4"; +$el-var-male: "\f1a5"; +$el-var-map-marker: "\f1a7"; +$el-var-map-marker-alt: "\f1a6"; +$el-var-mic: "\f1a9"; +$el-var-mic-alt: "\f1a8"; +$el-var-minus: "\f1ab"; +$el-var-minus-sign: "\f1aa"; +$el-var-move: "\f1ac"; +$el-var-music: "\f1ad"; +$el-var-myspace: "\f1ae"; +$el-var-network: "\f1af"; +$el-var-off: "\f1b0"; +$el-var-ok: "\f1b3"; +$el-var-ok-circle: "\f1b1"; +$el-var-ok-sign: "\f1b2"; +$el-var-opensource: "\f1b4"; +$el-var-paper-clip: "\f1b6"; +$el-var-paper-clip-alt: "\f1b5"; +$el-var-path: "\f1b7"; +$el-var-pause: "\f1b9"; +$el-var-pause-alt: "\f1b8"; +$el-var-pencil: "\f1bb"; +$el-var-pencil-alt: "\f1ba"; +$el-var-person: "\f1bc"; +$el-var-phone: "\f1be"; +$el-var-phone-alt: "\f1bd"; +$el-var-photo: "\f1c0"; +$el-var-photo-alt: "\f1bf"; +$el-var-picasa: "\f1c1"; +$el-var-picture: "\f1c2"; +$el-var-pinterest: "\f1c3"; +$el-var-plane: "\f1c4"; +$el-var-play: "\f1c7"; +$el-var-play-alt: "\f1c5"; +$el-var-play-circle: "\f1c6"; +$el-var-plurk: "\f1c9"; +$el-var-plurk-alt: "\f1c8"; +$el-var-plus: "\f1cb"; +$el-var-plus-sign: "\f1ca"; +$el-var-podcast: "\f1cc"; +$el-var-print: "\f1cd"; +$el-var-puzzle: "\f1ce"; +$el-var-qrcode: "\f1cf"; +$el-var-question: "\f1d1"; +$el-var-question-sign: "\f1d0"; +$el-var-quote-alt: "\f1d2"; +$el-var-quote-right: "\f1d4"; +$el-var-quote-right-alt: "\f1d3"; +$el-var-quotes: "\f1d5"; +$el-var-random: "\f1d6"; +$el-var-record: "\f1d7"; +$el-var-reddit: "\f1d8"; +$el-var-redux: "\f1d9"; +$el-var-refresh: "\f1da"; +$el-var-remove: "\f1dd"; +$el-var-remove-circle: "\f1db"; +$el-var-remove-sign: "\f1dc"; +$el-var-repeat: "\f1df"; +$el-var-repeat-alt: "\f1de"; +$el-var-resize-full: "\f1e0"; +$el-var-resize-horizontal: "\f1e1"; +$el-var-resize-small: "\f1e2"; +$el-var-resize-vertical: "\f1e3"; +$el-var-return-key: "\f1e4"; +$el-var-retweet: "\f1e5"; +$el-var-reverse-alt: "\f1e6"; +$el-var-road: "\f1e7"; +$el-var-rss: "\f1e8"; +$el-var-scissors: "\f1e9"; +$el-var-screen: "\f1eb"; +$el-var-screen-alt: "\f1ea"; +$el-var-screenshot: "\f1ec"; +$el-var-search: "\f1ee"; +$el-var-search-alt: "\f1ed"; +$el-var-share: "\f1f0"; +$el-var-share-alt: "\f1ef"; +$el-var-shopping-cart: "\f1f2"; +$el-var-shopping-cart-sign: "\f1f1"; +$el-var-signal: "\f1f3"; +$el-var-skype: "\f1f4"; +$el-var-slideshare: "\f1f5"; +$el-var-smiley: "\f1f7"; +$el-var-smiley-alt: "\f1f6"; +$el-var-soundcloud: "\f1f8"; +$el-var-speaker: "\f1f9"; +$el-var-spotify: "\f1fa"; +$el-var-stackoverflow: "\f1fb"; +$el-var-star: "\f1fe"; +$el-var-star-alt: "\f1fc"; +$el-var-star-empty: "\f1fd"; +$el-var-step-backward: "\f1ff"; +$el-var-step-forward: "\f200"; +$el-var-stop: "\f202"; +$el-var-stop-alt: "\f201"; +$el-var-stumbleupon: "\f203"; +$el-var-tag: "\f204"; +$el-var-tags: "\f205"; +$el-var-tasks: "\f206"; +$el-var-text-height: "\f207"; +$el-var-text-width: "\f208"; +$el-var-th: "\f20b"; +$el-var-th-large: "\f209"; +$el-var-th-list: "\f20a"; +$el-var-thumbs-down: "\f20c"; +$el-var-thumbs-up: "\f20d"; +$el-var-time: "\f20f"; +$el-var-time-alt: "\f20e"; +$el-var-tint: "\f210"; +$el-var-torso: "\f211"; +$el-var-trash: "\f213"; +$el-var-trash-alt: "\f212"; +$el-var-tumblr: "\f214"; +$el-var-twitter: "\f215"; +$el-var-universal-access: "\f216"; +$el-var-unlock: "\f218"; +$el-var-unlock-alt: "\f217"; +$el-var-upload: "\f219"; +$el-var-usd: "\f21a"; +$el-var-user: "\f21b"; +$el-var-viadeo: "\f21c"; +$el-var-video: "\f21f"; +$el-var-video-alt: "\f21d"; +$el-var-video-chat: "\f21e"; +$el-var-view-mode: "\f220"; +$el-var-vimeo: "\f221"; +$el-var-vkontakte: "\f222"; +$el-var-volume-down: "\f223"; +$el-var-volume-off: "\f224"; +$el-var-volume-up: "\f225"; +$el-var-w3c: "\f226"; +$el-var-warning-sign: "\f227"; +$el-var-website: "\f229"; +$el-var-website-alt: "\f228"; +$el-var-wheelchair: "\f22a"; +$el-var-wordpress: "\f22b"; +$el-var-wrench: "\f22d"; +$el-var-wrench-alt: "\f22c"; +$el-var-youtube: "\f22e"; +$el-var-zoom-in: "\f22f"; +$el-var-zoom-out: "\f230"; + diff --git a/Public/styles/elusive-icons/scss/elusive-icons.scss b/Public/styles/elusive-icons/scss/elusive-icons.scss new file mode 100644 index 0000000..c3bb7ba --- /dev/null +++ b/Public/styles/elusive-icons/scss/elusive-icons.scss @@ -0,0 +1,17 @@ +/*! + * Elusive Icons 2.0.0 by @ReduxFramework - http://elusiveicons.com - @reduxframework + * License - http://elusiveicons.com/license (Font: SIL OFL 1.1, CSS: MIT License) + */ + +@import "variables"; +@import "mixins"; +@import "path"; +@import "core"; +@import "larger"; +@import "fixed-width"; +@import "list"; +@import "bordered-pulled"; +@import "animated"; +@import "rotated-flipped"; +@import "stacked"; +@import "icons"; diff --git a/Public/styles/milligram.css b/Public/styles/milligram.css new file mode 100644 index 0000000..8118dee --- /dev/null +++ b/Public/styles/milligram.css @@ -0,0 +1,635 @@ +/*! + * Milligram v1.4.1 + * https://milligram.io + * + * Copyright (c) 2020 CJ Patoilo + * Licensed under the MIT license + */ + +*, +*:after, +*:before { + box-sizing: inherit; +} + +html { + box-sizing: border-box; + font-size: 62.5%; +} + +body { + color: #606c76; + font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif; + font-size: 1.6em; + font-weight: 300; + letter-spacing: .01em; + line-height: 1.6; +} + +blockquote { + border-left: 0.3rem solid #d1d1d1; + margin-left: 0; + margin-right: 0; + padding: 1rem 1.5rem; +} + +blockquote *:last-child { + margin-bottom: 0; +} + +.button, +button, +input[type='button'], +input[type='reset'], +input[type='submit'] { + background-color: #9b4dca; + border: 0.1rem solid #9b4dca; + border-radius: .4rem; + color: #fff; + cursor: pointer; + display: inline-block; + font-size: 1.1rem; + font-weight: 700; + height: 3.8rem; + letter-spacing: .1rem; + line-height: 3.8rem; + padding: 0 3.0rem; + text-align: center; + text-decoration: none; + text-transform: uppercase; + white-space: nowrap; +} + +.button:focus, .button:hover, +button:focus, +button:hover, +input[type='button']:focus, +input[type='button']:hover, +input[type='reset']:focus, +input[type='reset']:hover, +input[type='submit']:focus, +input[type='submit']:hover { + background-color: #606c76; + border-color: #606c76; + color: #fff; + outline: 0; +} + +.button[disabled], +button[disabled], +input[type='button'][disabled], +input[type='reset'][disabled], +input[type='submit'][disabled] { + cursor: default; + opacity: .5; +} + +.button[disabled]:focus, .button[disabled]:hover, +button[disabled]:focus, +button[disabled]:hover, +input[type='button'][disabled]:focus, +input[type='button'][disabled]:hover, +input[type='reset'][disabled]:focus, +input[type='reset'][disabled]:hover, +input[type='submit'][disabled]:focus, +input[type='submit'][disabled]:hover { + background-color: #9b4dca; + border-color: #9b4dca; +} + +.button.button-outline, +button.button-outline, +input[type='button'].button-outline, +input[type='reset'].button-outline, +input[type='submit'].button-outline { + background-color: transparent; + color: #9b4dca; +} + +.button.button-outline:focus, .button.button-outline:hover, +button.button-outline:focus, +button.button-outline:hover, +input[type='button'].button-outline:focus, +input[type='button'].button-outline:hover, +input[type='reset'].button-outline:focus, +input[type='reset'].button-outline:hover, +input[type='submit'].button-outline:focus, +input[type='submit'].button-outline:hover { + background-color: transparent; + border-color: #606c76; + color: #606c76; +} + +.button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover, +button.button-outline[disabled]:focus, +button.button-outline[disabled]:hover, +input[type='button'].button-outline[disabled]:focus, +input[type='button'].button-outline[disabled]:hover, +input[type='reset'].button-outline[disabled]:focus, +input[type='reset'].button-outline[disabled]:hover, +input[type='submit'].button-outline[disabled]:focus, +input[type='submit'].button-outline[disabled]:hover { + border-color: inherit; + color: #9b4dca; +} + +.button.button-clear, +button.button-clear, +input[type='button'].button-clear, +input[type='reset'].button-clear, +input[type='submit'].button-clear { + background-color: transparent; + border-color: transparent; + color: #9b4dca; +} + +.button.button-clear:focus, .button.button-clear:hover, +button.button-clear:focus, +button.button-clear:hover, +input[type='button'].button-clear:focus, +input[type='button'].button-clear:hover, +input[type='reset'].button-clear:focus, +input[type='reset'].button-clear:hover, +input[type='submit'].button-clear:focus, +input[type='submit'].button-clear:hover { + background-color: transparent; + border-color: transparent; + color: #606c76; +} + +.button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover, +button.button-clear[disabled]:focus, +button.button-clear[disabled]:hover, +input[type='button'].button-clear[disabled]:focus, +input[type='button'].button-clear[disabled]:hover, +input[type='reset'].button-clear[disabled]:focus, +input[type='reset'].button-clear[disabled]:hover, +input[type='submit'].button-clear[disabled]:focus, +input[type='submit'].button-clear[disabled]:hover { + color: #9b4dca; +} + +code { + background: #f4f5f6; + border-radius: .4rem; + font-size: 86%; + margin: 0 .2rem; + padding: .2rem .5rem; + white-space: nowrap; +} + +pre { + background: #f4f5f6; + border-left: 0.3rem solid #9b4dca; + overflow-y: hidden; +} + +pre > code { + border-radius: 0; + display: block; + padding: 1rem 1.5rem; + white-space: pre; +} + +hr { + border: 0; + border-top: 0.1rem solid #f4f5f6; + margin: 3.0rem 0; +} + +input[type='color'], +input[type='date'], +input[type='datetime'], +input[type='datetime-local'], +input[type='email'], +input[type='month'], +input[type='number'], +input[type='password'], +input[type='search'], +input[type='tel'], +input[type='text'], +input[type='url'], +input[type='week'], +input:not([type]), +textarea, +select { + -webkit-appearance: none; + background-color: transparent; + border: 0.1rem solid #d1d1d1; + border-radius: .4rem; + box-shadow: none; + box-sizing: inherit; + height: 3.8rem; + padding: .6rem 1.0rem .7rem; + width: 100%; +} + +input[type='color']:focus, +input[type='date']:focus, +input[type='datetime']:focus, +input[type='datetime-local']:focus, +input[type='email']:focus, +input[type='month']:focus, +input[type='number']:focus, +input[type='password']:focus, +input[type='search']:focus, +input[type='tel']:focus, +input[type='text']:focus, +input[type='url']:focus, +input[type='week']:focus, +input:not([type]):focus, +textarea:focus, +select:focus { + border-color: #9b4dca; + outline: 0; +} + +select { + background: url('data:image/svg+xml;utf8,') center right no-repeat; + padding-right: 3.0rem; +} + +select:focus { + background-image: url('data:image/svg+xml;utf8,'); +} + +select[multiple] { + background: none; + height: auto; +} + +textarea { + min-height: 6.5rem; +} + +label, +legend { + display: block; + font-size: 1.6rem; + font-weight: 700; + margin-bottom: .5rem; +} + +fieldset { + border-width: 0; + padding: 0; +} + +input[type='checkbox'], +input[type='radio'] { + display: inline; +} + +.label-inline { + display: inline-block; + font-weight: normal; + margin-left: .5rem; +} + +.container { + margin: 0 auto; + max-width: 112.0rem; + padding: 0 2.0rem; + position: relative; + width: 100%; +} + +.row { + display: flex; + flex-direction: column; + padding: 0; + width: 100%; +} + +.row.row-no-padding { + padding: 0; +} + +.row.row-no-padding > .column { + padding: 0; +} + +.row.row-wrap { + flex-wrap: wrap; +} + +.row.row-top { + align-items: flex-start; +} + +.row.row-bottom { + align-items: flex-end; +} + +.row.row-center { + align-items: center; +} + +.row.row-stretch { + align-items: stretch; +} + +.row.row-baseline { + align-items: baseline; +} + +.row .column { + display: block; + flex: 1 1 auto; + margin-left: 0; + max-width: 100%; + width: 100%; +} + +.row .column.column-offset-10 { + margin-left: 10%; +} + +.row .column.column-offset-20 { + margin-left: 20%; +} + +.row .column.column-offset-25 { + margin-left: 25%; +} + +.row .column.column-offset-33, .row .column.column-offset-34 { + margin-left: 33.3333%; +} + +.row .column.column-offset-40 { + margin-left: 40%; +} + +.row .column.column-offset-50 { + margin-left: 50%; +} + +.row .column.column-offset-60 { + margin-left: 60%; +} + +.row .column.column-offset-66, .row .column.column-offset-67 { + margin-left: 66.6666%; +} + +.row .column.column-offset-75 { + margin-left: 75%; +} + +.row .column.column-offset-80 { + margin-left: 80%; +} + +.row .column.column-offset-90 { + margin-left: 90%; +} + +.row .column.column-10 { + flex: 0 0 10%; + max-width: 10%; +} + +.row .column.column-20 { + flex: 0 0 20%; + max-width: 20%; +} + +.row .column.column-25 { + flex: 0 0 25%; + max-width: 25%; +} + +.row .column.column-33, .row .column.column-34 { + flex: 0 0 33.3333%; + max-width: 33.3333%; +} + +.row .column.column-40 { + flex: 0 0 40%; + max-width: 40%; +} + +.row .column.column-50 { + flex: 0 0 50%; + max-width: 50%; +} + +.row .column.column-60 { + flex: 0 0 60%; + max-width: 60%; +} + +.row .column.column-66, .row .column.column-67 { + flex: 0 0 66.6666%; + max-width: 66.6666%; +} + +.row .column.column-75 { + flex: 0 0 75%; + max-width: 75%; +} + +.row .column.column-80 { + flex: 0 0 80%; + max-width: 80%; +} + +.row .column.column-90 { + flex: 0 0 90%; + max-width: 90%; +} + +.row .column .column-top { + align-self: flex-start; +} + +.row .column .column-bottom { + align-self: flex-end; +} + +.row .column .column-center { + align-self: center; +} + +@media (min-width: 40rem) { + .row { + flex-direction: row; + margin-left: -1.0rem; + width: calc(100% + 2.0rem); + } + .row .column { + margin-bottom: inherit; + padding: 0 1.0rem; + } +} + +a { + color: #9b4dca; + text-decoration: none; +} + +a:focus, a:hover { + color: #606c76; +} + +dl, +ol, +ul { + list-style: none; + margin-top: 0; + padding-left: 0; +} + +dl dl, +dl ol, +dl ul, +ol dl, +ol ol, +ol ul, +ul dl, +ul ol, +ul ul { + font-size: 90%; + margin: 1.5rem 0 1.5rem 3.0rem; +} + +ol { + list-style: decimal inside; +} + +ul { + list-style: circle inside; +} + +.button, +button, +dd, +dt, +li { + margin-bottom: 1.0rem; +} + +fieldset, +input, +select, +textarea { + margin-bottom: 1.5rem; +} + +blockquote, +dl, +figure, +form, +ol, +p, +pre, +table, +ul { + margin-bottom: 2.5rem; +} + +table { + border-spacing: 0; + display: block; + overflow-x: auto; + text-align: left; + width: 100%; +} + +td, +th { + border-bottom: 0.1rem solid #e1e1e1; + padding: 1.2rem 1.5rem; +} + +td:first-child, +th:first-child { + padding-left: 0; +} + +td:last-child, +th:last-child { + padding-right: 0; +} + +@media (min-width: 40rem) { + table { + display: table; + overflow-x: initial; + } +} + +b, +strong { + font-weight: bold; +} + +p { + margin-top: 0; +} + +h1, +h2, +h3, +h4, +h5, +h6 { + font-weight: 300; + letter-spacing: -.1rem; + margin-bottom: 2.0rem; + margin-top: 0; +} + +h1 { + font-size: 4.6rem; + line-height: 1.2; +} + +h2 { + font-size: 3.6rem; + line-height: 1.25; +} + +h3 { + font-size: 2.8rem; + line-height: 1.3; +} + +h4 { + font-size: 2.2rem; + letter-spacing: -.08rem; + line-height: 1.35; +} + +h5 { + font-size: 1.8rem; + letter-spacing: -.05rem; + line-height: 1.5; +} + +h6 { + font-size: 1.6rem; + letter-spacing: 0; + line-height: 1.4; +} + +img { + max-width: 100%; +} + +.clearfix:after { + clear: both; + content: ' '; + display: table; +} + +.float-left { + float: left; +} + +.float-right { + float: right; +} + +/*# sourceMappingURL=milligram.css.map */ \ No newline at end of file diff --git a/Public/styles/milligram.css.map b/Public/styles/milligram.css.map new file mode 100644 index 0000000..3195274 --- /dev/null +++ b/Public/styles/milligram.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["milligram.css"],"names":[],"mappings":"AAAA;;;EAGE,mBAAmB;AACrB;;AAEA;EACE,sBAAsB;EACtB,gBAAgB;AAClB;;AAEA;EACE,cAAc;EACd,yEAAyE;EACzE,gBAAgB;EAChB,gBAAgB;EAChB,qBAAqB;EACrB,gBAAgB;AAClB;;AAEA;EACE,iCAAiC;EACjC,cAAc;EACd,eAAe;EACf,oBAAoB;AACtB;;AAEA;EACE,gBAAgB;AAClB;;AAEA;;;;;EAKE,yBAAyB;EACzB,4BAA4B;EAC5B,oBAAoB;EACpB,WAAW;EACX,eAAe;EACf,qBAAqB;EACrB,iBAAiB;EACjB,gBAAgB;EAChB,cAAc;EACd,qBAAqB;EACrB,mBAAmB;EACnB,iBAAiB;EACjB,kBAAkB;EAClB,qBAAqB;EACrB,yBAAyB;EACzB,mBAAmB;AACrB;;AAEA;;;;;;;;;EASE,yBAAyB;EACzB,qBAAqB;EACrB,WAAW;EACX,UAAU;AACZ;;AAEA;;;;;EAKE,eAAe;EACf,WAAW;AACb;;AAEA;;;;;;;;;EASE,yBAAyB;EACzB,qBAAqB;AACvB;;AAEA;;;;;EAKE,6BAA6B;EAC7B,cAAc;AAChB;;AAEA;;;;;;;;;EASE,6BAA6B;EAC7B,qBAAqB;EACrB,cAAc;AAChB;;AAEA;;;;;;;;;EASE,qBAAqB;EACrB,cAAc;AAChB;;AAEA;;;;;EAKE,6BAA6B;EAC7B,yBAAyB;EACzB,cAAc;AAChB;;AAEA;;;;;;;;;EASE,6BAA6B;EAC7B,yBAAyB;EACzB,cAAc;AAChB;;AAEA;;;;;;;;;EASE,cAAc;AAChB;;AAEA;EACE,mBAAmB;EACnB,oBAAoB;EACpB,cAAc;EACd,eAAe;EACf,oBAAoB;EACpB,mBAAmB;AACrB;;AAEA;EACE,mBAAmB;EACnB,iCAAiC;EACjC,kBAAkB;AACpB;;AAEA;EACE,gBAAgB;EAChB,cAAc;EACd,oBAAoB;EACpB,gBAAgB;AAClB;;AAEA;EACE,SAAS;EACT,gCAAgC;EAChC,gBAAgB;AAClB;;AAEA;;;;;;;;;;;;;;;;EAgBE,wBAAwB;EACxB,6BAA6B;EAC7B,4BAA4B;EAC5B,oBAAoB;EACpB,gBAAgB;EAChB,mBAAmB;EACnB,cAAc;EACd,2BAA2B;EAC3B,WAAW;AACb;;AAEA;;;;;;;;;;;;;;;;EAgBE,qBAAqB;EACrB,UAAU;AACZ;;AAEA;EACE,uLAAuL;EACvL,qBAAqB;AACvB;;AAEA;EACE,sKAAsK;AACxK;;AAEA;EACE,gBAAgB;EAChB,YAAY;AACd;;AAEA;EACE,kBAAkB;AACpB;;AAEA;;EAEE,cAAc;EACd,iBAAiB;EACjB,gBAAgB;EAChB,oBAAoB;AACtB;;AAEA;EACE,eAAe;EACf,UAAU;AACZ;;AAEA;;EAEE,eAAe;AACjB;;AAEA;EACE,qBAAqB;EACrB,mBAAmB;EACnB,kBAAkB;AACpB;;AAEA;EACE,cAAc;EACd,mBAAmB;EACnB,iBAAiB;EACjB,kBAAkB;EAClB,WAAW;AACb;;AAEA;EACE,aAAa;EACb,sBAAsB;EACtB,UAAU;EACV,WAAW;AACb;;AAEA;EACE,UAAU;AACZ;;AAEA;EACE,UAAU;AACZ;;AAEA;EACE,eAAe;AACjB;;AAEA;EACE,uBAAuB;AACzB;;AAEA;EACE,qBAAqB;AACvB;;AAEA;EACE,mBAAmB;AACrB;;AAEA;EACE,oBAAoB;AACtB;;AAEA;EACE,qBAAqB;AACvB;;AAEA;EACE,cAAc;EACd,cAAc;EACd,cAAc;EACd,eAAe;EACf,WAAW;AACb;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,qBAAqB;AACvB;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,qBAAqB;AACvB;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,gBAAgB;AAClB;;AAEA;EACE,aAAa;EACb,cAAc;AAChB;;AAEA;EACE,aAAa;EACb,cAAc;AAChB;;AAEA;EACE,aAAa;EACb,cAAc;AAChB;;AAEA;EACE,kBAAkB;EAClB,mBAAmB;AACrB;;AAEA;EACE,aAAa;EACb,cAAc;AAChB;;AAEA;EACE,aAAa;EACb,cAAc;AAChB;;AAEA;EACE,aAAa;EACb,cAAc;AAChB;;AAEA;EACE,kBAAkB;EAClB,mBAAmB;AACrB;;AAEA;EACE,aAAa;EACb,cAAc;AAChB;;AAEA;EACE,aAAa;EACb,cAAc;AAChB;;AAEA;EACE,aAAa;EACb,cAAc;AAChB;;AAEA;EACE,sBAAsB;AACxB;;AAEA;EACE,oBAAoB;AACtB;;AAEA;EACE,kBAAkB;AACpB;;AAEA;EACE;IACE,mBAAmB;IACnB,oBAAoB;IACpB,0BAA0B;EAC5B;EACA;IACE,sBAAsB;IACtB,iBAAiB;EACnB;AACF;;AAEA;EACE,cAAc;EACd,qBAAqB;AACvB;;AAEA;EACE,cAAc;AAChB;;AAEA;;;EAGE,gBAAgB;EAChB,aAAa;EACb,eAAe;AACjB;;AAEA;;;;;;;;;EASE,cAAc;EACd,8BAA8B;AAChC;;AAEA;EACE,0BAA0B;AAC5B;;AAEA;EACE,yBAAyB;AAC3B;;AAEA;;;;;EAKE,qBAAqB;AACvB;;AAEA;;;;EAIE,qBAAqB;AACvB;;AAEA;;;;;;;;;EASE,qBAAqB;AACvB;;AAEA;EACE,iBAAiB;EACjB,cAAc;EACd,gBAAgB;EAChB,gBAAgB;EAChB,WAAW;AACb;;AAEA;;EAEE,mCAAmC;EACnC,sBAAsB;AACxB;;AAEA;;EAEE,eAAe;AACjB;;AAEA;;EAEE,gBAAgB;AAClB;;AAEA;EACE;IACE,cAAc;IACd,mBAAmB;EACrB;AACF;;AAEA;;EAEE,iBAAiB;AACnB;;AAEA;EACE,aAAa;AACf;;AAEA;;;;;;EAME,gBAAgB;EAChB,sBAAsB;EACtB,qBAAqB;EACrB,aAAa;AACf;;AAEA;EACE,iBAAiB;EACjB,gBAAgB;AAClB;;AAEA;EACE,iBAAiB;EACjB,iBAAiB;AACnB;;AAEA;EACE,iBAAiB;EACjB,gBAAgB;AAClB;;AAEA;EACE,iBAAiB;EACjB,uBAAuB;EACvB,iBAAiB;AACnB;;AAEA;EACE,iBAAiB;EACjB,uBAAuB;EACvB,gBAAgB;AAClB;;AAEA;EACE,iBAAiB;EACjB,iBAAiB;EACjB,gBAAgB;AAClB;;AAEA;EACE,eAAe;AACjB;;AAEA;EACE,WAAW;EACX,YAAY;EACZ,cAAc;AAChB;;AAEA;EACE,WAAW;AACb;;AAEA;EACE,YAAY;AACd","file":"milligram.css","sourcesContent":["*,\n*:after,\n*:before {\n box-sizing: inherit;\n}\n\nhtml {\n box-sizing: border-box;\n font-size: 62.5%;\n}\n\nbody {\n color: #606c76;\n font-family: 'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;\n font-size: 1.6em;\n font-weight: 300;\n letter-spacing: .01em;\n line-height: 1.6;\n}\n\nblockquote {\n border-left: 0.3rem solid #d1d1d1;\n margin-left: 0;\n margin-right: 0;\n padding: 1rem 1.5rem;\n}\n\nblockquote *:last-child {\n margin-bottom: 0;\n}\n\n.button,\nbutton,\ninput[type='button'],\ninput[type='reset'],\ninput[type='submit'] {\n background-color: #9b4dca;\n border: 0.1rem solid #9b4dca;\n border-radius: .4rem;\n color: #fff;\n cursor: pointer;\n display: inline-block;\n font-size: 1.1rem;\n font-weight: 700;\n height: 3.8rem;\n letter-spacing: .1rem;\n line-height: 3.8rem;\n padding: 0 3.0rem;\n text-align: center;\n text-decoration: none;\n text-transform: uppercase;\n white-space: nowrap;\n}\n\n.button:focus, .button:hover,\nbutton:focus,\nbutton:hover,\ninput[type='button']:focus,\ninput[type='button']:hover,\ninput[type='reset']:focus,\ninput[type='reset']:hover,\ninput[type='submit']:focus,\ninput[type='submit']:hover {\n background-color: #606c76;\n border-color: #606c76;\n color: #fff;\n outline: 0;\n}\n\n.button[disabled],\nbutton[disabled],\ninput[type='button'][disabled],\ninput[type='reset'][disabled],\ninput[type='submit'][disabled] {\n cursor: default;\n opacity: .5;\n}\n\n.button[disabled]:focus, .button[disabled]:hover,\nbutton[disabled]:focus,\nbutton[disabled]:hover,\ninput[type='button'][disabled]:focus,\ninput[type='button'][disabled]:hover,\ninput[type='reset'][disabled]:focus,\ninput[type='reset'][disabled]:hover,\ninput[type='submit'][disabled]:focus,\ninput[type='submit'][disabled]:hover {\n background-color: #9b4dca;\n border-color: #9b4dca;\n}\n\n.button.button-outline,\nbutton.button-outline,\ninput[type='button'].button-outline,\ninput[type='reset'].button-outline,\ninput[type='submit'].button-outline {\n background-color: transparent;\n color: #9b4dca;\n}\n\n.button.button-outline:focus, .button.button-outline:hover,\nbutton.button-outline:focus,\nbutton.button-outline:hover,\ninput[type='button'].button-outline:focus,\ninput[type='button'].button-outline:hover,\ninput[type='reset'].button-outline:focus,\ninput[type='reset'].button-outline:hover,\ninput[type='submit'].button-outline:focus,\ninput[type='submit'].button-outline:hover {\n background-color: transparent;\n border-color: #606c76;\n color: #606c76;\n}\n\n.button.button-outline[disabled]:focus, .button.button-outline[disabled]:hover,\nbutton.button-outline[disabled]:focus,\nbutton.button-outline[disabled]:hover,\ninput[type='button'].button-outline[disabled]:focus,\ninput[type='button'].button-outline[disabled]:hover,\ninput[type='reset'].button-outline[disabled]:focus,\ninput[type='reset'].button-outline[disabled]:hover,\ninput[type='submit'].button-outline[disabled]:focus,\ninput[type='submit'].button-outline[disabled]:hover {\n border-color: inherit;\n color: #9b4dca;\n}\n\n.button.button-clear,\nbutton.button-clear,\ninput[type='button'].button-clear,\ninput[type='reset'].button-clear,\ninput[type='submit'].button-clear {\n background-color: transparent;\n border-color: transparent;\n color: #9b4dca;\n}\n\n.button.button-clear:focus, .button.button-clear:hover,\nbutton.button-clear:focus,\nbutton.button-clear:hover,\ninput[type='button'].button-clear:focus,\ninput[type='button'].button-clear:hover,\ninput[type='reset'].button-clear:focus,\ninput[type='reset'].button-clear:hover,\ninput[type='submit'].button-clear:focus,\ninput[type='submit'].button-clear:hover {\n background-color: transparent;\n border-color: transparent;\n color: #606c76;\n}\n\n.button.button-clear[disabled]:focus, .button.button-clear[disabled]:hover,\nbutton.button-clear[disabled]:focus,\nbutton.button-clear[disabled]:hover,\ninput[type='button'].button-clear[disabled]:focus,\ninput[type='button'].button-clear[disabled]:hover,\ninput[type='reset'].button-clear[disabled]:focus,\ninput[type='reset'].button-clear[disabled]:hover,\ninput[type='submit'].button-clear[disabled]:focus,\ninput[type='submit'].button-clear[disabled]:hover {\n color: #9b4dca;\n}\n\ncode {\n background: #f4f5f6;\n border-radius: .4rem;\n font-size: 86%;\n margin: 0 .2rem;\n padding: .2rem .5rem;\n white-space: nowrap;\n}\n\npre {\n background: #f4f5f6;\n border-left: 0.3rem solid #9b4dca;\n overflow-y: hidden;\n}\n\npre > code {\n border-radius: 0;\n display: block;\n padding: 1rem 1.5rem;\n white-space: pre;\n}\n\nhr {\n border: 0;\n border-top: 0.1rem solid #f4f5f6;\n margin: 3.0rem 0;\n}\n\ninput[type='color'],\ninput[type='date'],\ninput[type='datetime'],\ninput[type='datetime-local'],\ninput[type='email'],\ninput[type='month'],\ninput[type='number'],\ninput[type='password'],\ninput[type='search'],\ninput[type='tel'],\ninput[type='text'],\ninput[type='url'],\ninput[type='week'],\ninput:not([type]),\ntextarea,\nselect {\n -webkit-appearance: none;\n background-color: transparent;\n border: 0.1rem solid #d1d1d1;\n border-radius: .4rem;\n box-shadow: none;\n box-sizing: inherit;\n height: 3.8rem;\n padding: .6rem 1.0rem .7rem;\n width: 100%;\n}\n\ninput[type='color']:focus,\ninput[type='date']:focus,\ninput[type='datetime']:focus,\ninput[type='datetime-local']:focus,\ninput[type='email']:focus,\ninput[type='month']:focus,\ninput[type='number']:focus,\ninput[type='password']:focus,\ninput[type='search']:focus,\ninput[type='tel']:focus,\ninput[type='text']:focus,\ninput[type='url']:focus,\ninput[type='week']:focus,\ninput:not([type]):focus,\ntextarea:focus,\nselect:focus {\n border-color: #9b4dca;\n outline: 0;\n}\n\nselect {\n background: url('data:image/svg+xml;utf8,') center right no-repeat;\n padding-right: 3.0rem;\n}\n\nselect:focus {\n background-image: url('data:image/svg+xml;utf8,');\n}\n\nselect[multiple] {\n background: none;\n height: auto;\n}\n\ntextarea {\n min-height: 6.5rem;\n}\n\nlabel,\nlegend {\n display: block;\n font-size: 1.6rem;\n font-weight: 700;\n margin-bottom: .5rem;\n}\n\nfieldset {\n border-width: 0;\n padding: 0;\n}\n\ninput[type='checkbox'],\ninput[type='radio'] {\n display: inline;\n}\n\n.label-inline {\n display: inline-block;\n font-weight: normal;\n margin-left: .5rem;\n}\n\n.container {\n margin: 0 auto;\n max-width: 112.0rem;\n padding: 0 2.0rem;\n position: relative;\n width: 100%;\n}\n\n.row {\n display: flex;\n flex-direction: column;\n padding: 0;\n width: 100%;\n}\n\n.row.row-no-padding {\n padding: 0;\n}\n\n.row.row-no-padding > .column {\n padding: 0;\n}\n\n.row.row-wrap {\n flex-wrap: wrap;\n}\n\n.row.row-top {\n align-items: flex-start;\n}\n\n.row.row-bottom {\n align-items: flex-end;\n}\n\n.row.row-center {\n align-items: center;\n}\n\n.row.row-stretch {\n align-items: stretch;\n}\n\n.row.row-baseline {\n align-items: baseline;\n}\n\n.row .column {\n display: block;\n flex: 1 1 auto;\n margin-left: 0;\n max-width: 100%;\n width: 100%;\n}\n\n.row .column.column-offset-10 {\n margin-left: 10%;\n}\n\n.row .column.column-offset-20 {\n margin-left: 20%;\n}\n\n.row .column.column-offset-25 {\n margin-left: 25%;\n}\n\n.row .column.column-offset-33, .row .column.column-offset-34 {\n margin-left: 33.3333%;\n}\n\n.row .column.column-offset-40 {\n margin-left: 40%;\n}\n\n.row .column.column-offset-50 {\n margin-left: 50%;\n}\n\n.row .column.column-offset-60 {\n margin-left: 60%;\n}\n\n.row .column.column-offset-66, .row .column.column-offset-67 {\n margin-left: 66.6666%;\n}\n\n.row .column.column-offset-75 {\n margin-left: 75%;\n}\n\n.row .column.column-offset-80 {\n margin-left: 80%;\n}\n\n.row .column.column-offset-90 {\n margin-left: 90%;\n}\n\n.row .column.column-10 {\n flex: 0 0 10%;\n max-width: 10%;\n}\n\n.row .column.column-20 {\n flex: 0 0 20%;\n max-width: 20%;\n}\n\n.row .column.column-25 {\n flex: 0 0 25%;\n max-width: 25%;\n}\n\n.row .column.column-33, .row .column.column-34 {\n flex: 0 0 33.3333%;\n max-width: 33.3333%;\n}\n\n.row .column.column-40 {\n flex: 0 0 40%;\n max-width: 40%;\n}\n\n.row .column.column-50 {\n flex: 0 0 50%;\n max-width: 50%;\n}\n\n.row .column.column-60 {\n flex: 0 0 60%;\n max-width: 60%;\n}\n\n.row .column.column-66, .row .column.column-67 {\n flex: 0 0 66.6666%;\n max-width: 66.6666%;\n}\n\n.row .column.column-75 {\n flex: 0 0 75%;\n max-width: 75%;\n}\n\n.row .column.column-80 {\n flex: 0 0 80%;\n max-width: 80%;\n}\n\n.row .column.column-90 {\n flex: 0 0 90%;\n max-width: 90%;\n}\n\n.row .column .column-top {\n align-self: flex-start;\n}\n\n.row .column .column-bottom {\n align-self: flex-end;\n}\n\n.row .column .column-center {\n align-self: center;\n}\n\n@media (min-width: 40rem) {\n .row {\n flex-direction: row;\n margin-left: -1.0rem;\n width: calc(100% + 2.0rem);\n }\n .row .column {\n margin-bottom: inherit;\n padding: 0 1.0rem;\n }\n}\n\na {\n color: #9b4dca;\n text-decoration: none;\n}\n\na:focus, a:hover {\n color: #606c76;\n}\n\ndl,\nol,\nul {\n list-style: none;\n margin-top: 0;\n padding-left: 0;\n}\n\ndl dl,\ndl ol,\ndl ul,\nol dl,\nol ol,\nol ul,\nul dl,\nul ol,\nul ul {\n font-size: 90%;\n margin: 1.5rem 0 1.5rem 3.0rem;\n}\n\nol {\n list-style: decimal inside;\n}\n\nul {\n list-style: circle inside;\n}\n\n.button,\nbutton,\ndd,\ndt,\nli {\n margin-bottom: 1.0rem;\n}\n\nfieldset,\ninput,\nselect,\ntextarea {\n margin-bottom: 1.5rem;\n}\n\nblockquote,\ndl,\nfigure,\nform,\nol,\np,\npre,\ntable,\nul {\n margin-bottom: 2.5rem;\n}\n\ntable {\n border-spacing: 0;\n display: block;\n overflow-x: auto;\n text-align: left;\n width: 100%;\n}\n\ntd,\nth {\n border-bottom: 0.1rem solid #e1e1e1;\n padding: 1.2rem 1.5rem;\n}\n\ntd:first-child,\nth:first-child {\n padding-left: 0;\n}\n\ntd:last-child,\nth:last-child {\n padding-right: 0;\n}\n\n@media (min-width: 40rem) {\n table {\n display: table;\n overflow-x: initial;\n }\n}\n\nb,\nstrong {\n font-weight: bold;\n}\n\np {\n margin-top: 0;\n}\n\nh1,\nh2,\nh3,\nh4,\nh5,\nh6 {\n font-weight: 300;\n letter-spacing: -.1rem;\n margin-bottom: 2.0rem;\n margin-top: 0;\n}\n\nh1 {\n font-size: 4.6rem;\n line-height: 1.2;\n}\n\nh2 {\n font-size: 3.6rem;\n line-height: 1.25;\n}\n\nh3 {\n font-size: 2.8rem;\n line-height: 1.3;\n}\n\nh4 {\n font-size: 2.2rem;\n letter-spacing: -.08rem;\n line-height: 1.35;\n}\n\nh5 {\n font-size: 1.8rem;\n letter-spacing: -.05rem;\n line-height: 1.5;\n}\n\nh6 {\n font-size: 1.6rem;\n letter-spacing: 0;\n line-height: 1.4;\n}\n\nimg {\n max-width: 100%;\n}\n\n.clearfix:after {\n clear: both;\n content: ' ';\n display: table;\n}\n\n.float-left {\n float: left;\n}\n\n.float-right {\n float: right;\n}\n"]} \ No newline at end of file diff --git a/Public/styles/milligram.min.css b/Public/styles/milligram.min.css new file mode 100644 index 0000000..5e8955c --- /dev/null +++ b/Public/styles/milligram.min.css @@ -0,0 +1,3 @@ +*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#606c76;font-family:'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#9b4dca;border:0.1rem solid #9b4dca;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#9b4dca;border-color:#9b4dca}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#9b4dca}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#9b4dca}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#9b4dca}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#9b4dca}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #9b4dca;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='color'],input[type='date'],input[type='datetime'],input[type='datetime-local'],input[type='email'],input[type='month'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],input[type='week'],input:not([type]),textarea,select{-webkit-appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem .7rem;width:100%}input[type='color']:focus,input[type='date']:focus,input[type='datetime']:focus,input[type='datetime-local']:focus,input[type='email']:focus,input[type='month']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,input[type='week']:focus,input:not([type]):focus,textarea:focus,select:focus{border-color:#9b4dca;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}select[multiple]{background:none;height:auto}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-40{margin-left:40%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-60{margin-left:60%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#9b4dca;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;display:block;overflow-x:auto;text-align:left;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}@media (min-width: 40rem){table{display:table;overflow-x:initial}}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right} + +/*# sourceMappingURL=milligram.min.css.map */ \ No newline at end of file diff --git a/Public/styles/milligram.min.css.map b/Public/styles/milligram.min.css.map new file mode 100644 index 0000000..009da6a --- /dev/null +++ b/Public/styles/milligram.min.css.map @@ -0,0 +1 @@ +{"version":3,"sources":["milligram.min.css"],"names":[],"mappings":"AAAA,mBAAmB,kBAAkB,CAAC,KAAK,qBAAqB,CAAC,eAAe,CAAC,KAAK,aAAa,CAAC,wEAAwE,CAAC,eAAe,CAAC,eAAe,CAAC,oBAAoB,CAAC,eAAe,CAAC,WAAW,gCAAgC,CAAC,aAAa,CAAC,cAAc,CAAC,mBAAmB,CAAC,wBAAwB,eAAe,CAAC,6EAA6E,wBAAwB,CAAC,2BAA2B,CAAC,mBAAmB,CAAC,UAAU,CAAC,cAAc,CAAC,oBAAoB,CAAC,gBAAgB,CAAC,eAAe,CAAC,aAAa,CAAC,oBAAoB,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,oBAAoB,CAAC,wBAAwB,CAAC,kBAAkB,CAAC,sNAAsN,wBAAwB,CAAC,oBAAoB,CAAC,UAAU,CAAC,SAAS,CAAC,+HAA+H,cAAc,CAAC,UAAU,CAAC,0TAA0T,wBAAwB,CAAC,oBAAoB,CAAC,wJAAwJ,4BAA4B,CAAC,aAAa,CAAC,4WAA4W,4BAA4B,CAAC,oBAAoB,CAAC,aAAa,CAAC,gdAAgd,oBAAoB,CAAC,aAAa,CAAC,8IAA8I,4BAA4B,CAAC,wBAAwB,CAAC,aAAa,CAAC,wVAAwV,4BAA4B,CAAC,wBAAwB,CAAC,aAAa,CAAC,4bAA4b,aAAa,CAAC,KAAK,kBAAkB,CAAC,mBAAmB,CAAC,aAAa,CAAC,cAAc,CAAC,mBAAmB,CAAC,kBAAkB,CAAC,IAAI,kBAAkB,CAAC,gCAAgC,CAAC,iBAAiB,CAAC,SAAS,eAAe,CAAC,aAAa,CAAC,mBAAmB,CAAC,eAAe,CAAC,GAAG,QAAQ,CAAC,+BAA+B,CAAC,eAAe,CAAC,gTAAgT,uBAAuB,CAAC,4BAA4B,CAAC,2BAA2B,CAAC,mBAAmB,CAAC,eAAe,CAAC,kBAAkB,CAAC,aAAa,CAAC,0BAA0B,CAAC,UAAU,CAAC,gZAAgZ,oBAAoB,CAAC,SAAS,CAAC,OAAO,sLAAsL,CAAC,oBAAoB,CAAC,aAAa,qKAAqK,CAAC,iBAAiB,eAAe,CAAC,WAAW,CAAC,SAAS,iBAAiB,CAAC,aAAa,aAAa,CAAC,gBAAgB,CAAC,eAAe,CAAC,mBAAmB,CAAC,SAAS,cAAc,CAAC,SAAS,CAAC,2CAA2C,cAAc,CAAC,cAAc,oBAAoB,CAAC,kBAAkB,CAAC,iBAAiB,CAAC,WAAW,aAAa,CAAC,kBAAkB,CAAC,gBAAgB,CAAC,iBAAiB,CAAC,UAAU,CAAC,KAAK,YAAY,CAAC,qBAAqB,CAAC,SAAS,CAAC,UAAU,CAAC,oBAAoB,SAAS,CAAC,4BAA4B,SAAS,CAAC,cAAc,cAAc,CAAC,aAAa,sBAAsB,CAAC,gBAAgB,oBAAoB,CAAC,gBAAgB,kBAAkB,CAAC,iBAAiB,mBAAmB,CAAC,kBAAkB,oBAAoB,CAAC,aAAa,aAAa,CAAC,aAAa,CAAC,aAAa,CAAC,cAAc,CAAC,UAAU,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,4DAA4D,oBAAoB,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,4DAA4D,oBAAoB,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,8BAA8B,eAAe,CAAC,uBAAuB,YAAY,CAAC,aAAa,CAAC,uBAAuB,YAAY,CAAC,aAAa,CAAC,uBAAuB,YAAY,CAAC,aAAa,CAAC,8CAA8C,iBAAiB,CAAC,kBAAkB,CAAC,uBAAuB,YAAY,CAAC,aAAa,CAAC,uBAAuB,YAAY,CAAC,aAAa,CAAC,uBAAuB,YAAY,CAAC,aAAa,CAAC,8CAA8C,iBAAiB,CAAC,kBAAkB,CAAC,uBAAuB,YAAY,CAAC,aAAa,CAAC,uBAAuB,YAAY,CAAC,aAAa,CAAC,uBAAuB,YAAY,CAAC,aAAa,CAAC,yBAAyB,qBAAqB,CAAC,4BAA4B,mBAAmB,CAAC,4BAA4B,iBAAiB,CAAC,0BAA0B,KAAK,kBAAkB,CAAC,mBAAmB,CAAC,yBAAyB,CAAC,aAAa,qBAAqB,CAAC,gBAAgB,CAAC,CAAC,EAAE,aAAa,CAAC,oBAAoB,CAAC,gBAAgB,aAAa,CAAC,SAAS,eAAe,CAAC,YAAY,CAAC,cAAc,CAAC,sDAAsD,aAAa,CAAC,6BAA6B,CAAC,GAAG,yBAAyB,CAAC,GAAG,wBAAwB,CAAC,wBAAwB,oBAAoB,CAAC,+BAA+B,oBAAoB,CAAC,4CAA4C,oBAAoB,CAAC,MAAM,gBAAgB,CAAC,aAAa,CAAC,eAAe,CAAC,eAAe,CAAC,UAAU,CAAC,MAAM,kCAAkC,CAAC,qBAAqB,CAAC,8BAA8B,cAAc,CAAC,4BAA4B,eAAe,CAAC,0BAA0B,MAAM,aAAa,CAAC,kBAAkB,CAAC,CAAC,SAAS,gBAAgB,CAAC,EAAE,YAAY,CAAC,kBAAkB,eAAe,CAAC,qBAAqB,CAAC,oBAAoB,CAAC,YAAY,CAAC,GAAG,gBAAgB,CAAC,eAAe,CAAC,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC,eAAe,CAAC,GAAG,gBAAgB,CAAC,sBAAsB,CAAC,gBAAgB,CAAC,GAAG,gBAAgB,CAAC,sBAAsB,CAAC,eAAe,CAAC,GAAG,gBAAgB,CAAC,gBAAgB,CAAC,eAAe,CAAC,IAAI,cAAc,CAAC,gBAAgB,UAAU,CAAC,WAAW,CAAC,aAAa,CAAC,YAAY,UAAU,CAAC,aAAa,WAAW","file":"milligram.min.css","sourcesContent":["*,*:after,*:before{box-sizing:inherit}html{box-sizing:border-box;font-size:62.5%}body{color:#606c76;font-family:'Roboto', 'Helvetica Neue', 'Helvetica', 'Arial', sans-serif;font-size:1.6em;font-weight:300;letter-spacing:.01em;line-height:1.6}blockquote{border-left:0.3rem solid #d1d1d1;margin-left:0;margin-right:0;padding:1rem 1.5rem}blockquote *:last-child{margin-bottom:0}.button,button,input[type='button'],input[type='reset'],input[type='submit']{background-color:#9b4dca;border:0.1rem solid #9b4dca;border-radius:.4rem;color:#fff;cursor:pointer;display:inline-block;font-size:1.1rem;font-weight:700;height:3.8rem;letter-spacing:.1rem;line-height:3.8rem;padding:0 3.0rem;text-align:center;text-decoration:none;text-transform:uppercase;white-space:nowrap}.button:focus,.button:hover,button:focus,button:hover,input[type='button']:focus,input[type='button']:hover,input[type='reset']:focus,input[type='reset']:hover,input[type='submit']:focus,input[type='submit']:hover{background-color:#606c76;border-color:#606c76;color:#fff;outline:0}.button[disabled],button[disabled],input[type='button'][disabled],input[type='reset'][disabled],input[type='submit'][disabled]{cursor:default;opacity:.5}.button[disabled]:focus,.button[disabled]:hover,button[disabled]:focus,button[disabled]:hover,input[type='button'][disabled]:focus,input[type='button'][disabled]:hover,input[type='reset'][disabled]:focus,input[type='reset'][disabled]:hover,input[type='submit'][disabled]:focus,input[type='submit'][disabled]:hover{background-color:#9b4dca;border-color:#9b4dca}.button.button-outline,button.button-outline,input[type='button'].button-outline,input[type='reset'].button-outline,input[type='submit'].button-outline{background-color:transparent;color:#9b4dca}.button.button-outline:focus,.button.button-outline:hover,button.button-outline:focus,button.button-outline:hover,input[type='button'].button-outline:focus,input[type='button'].button-outline:hover,input[type='reset'].button-outline:focus,input[type='reset'].button-outline:hover,input[type='submit'].button-outline:focus,input[type='submit'].button-outline:hover{background-color:transparent;border-color:#606c76;color:#606c76}.button.button-outline[disabled]:focus,.button.button-outline[disabled]:hover,button.button-outline[disabled]:focus,button.button-outline[disabled]:hover,input[type='button'].button-outline[disabled]:focus,input[type='button'].button-outline[disabled]:hover,input[type='reset'].button-outline[disabled]:focus,input[type='reset'].button-outline[disabled]:hover,input[type='submit'].button-outline[disabled]:focus,input[type='submit'].button-outline[disabled]:hover{border-color:inherit;color:#9b4dca}.button.button-clear,button.button-clear,input[type='button'].button-clear,input[type='reset'].button-clear,input[type='submit'].button-clear{background-color:transparent;border-color:transparent;color:#9b4dca}.button.button-clear:focus,.button.button-clear:hover,button.button-clear:focus,button.button-clear:hover,input[type='button'].button-clear:focus,input[type='button'].button-clear:hover,input[type='reset'].button-clear:focus,input[type='reset'].button-clear:hover,input[type='submit'].button-clear:focus,input[type='submit'].button-clear:hover{background-color:transparent;border-color:transparent;color:#606c76}.button.button-clear[disabled]:focus,.button.button-clear[disabled]:hover,button.button-clear[disabled]:focus,button.button-clear[disabled]:hover,input[type='button'].button-clear[disabled]:focus,input[type='button'].button-clear[disabled]:hover,input[type='reset'].button-clear[disabled]:focus,input[type='reset'].button-clear[disabled]:hover,input[type='submit'].button-clear[disabled]:focus,input[type='submit'].button-clear[disabled]:hover{color:#9b4dca}code{background:#f4f5f6;border-radius:.4rem;font-size:86%;margin:0 .2rem;padding:.2rem .5rem;white-space:nowrap}pre{background:#f4f5f6;border-left:0.3rem solid #9b4dca;overflow-y:hidden}pre>code{border-radius:0;display:block;padding:1rem 1.5rem;white-space:pre}hr{border:0;border-top:0.1rem solid #f4f5f6;margin:3.0rem 0}input[type='color'],input[type='date'],input[type='datetime'],input[type='datetime-local'],input[type='email'],input[type='month'],input[type='number'],input[type='password'],input[type='search'],input[type='tel'],input[type='text'],input[type='url'],input[type='week'],input:not([type]),textarea,select{-webkit-appearance:none;background-color:transparent;border:0.1rem solid #d1d1d1;border-radius:.4rem;box-shadow:none;box-sizing:inherit;height:3.8rem;padding:.6rem 1.0rem .7rem;width:100%}input[type='color']:focus,input[type='date']:focus,input[type='datetime']:focus,input[type='datetime-local']:focus,input[type='email']:focus,input[type='month']:focus,input[type='number']:focus,input[type='password']:focus,input[type='search']:focus,input[type='tel']:focus,input[type='text']:focus,input[type='url']:focus,input[type='week']:focus,input:not([type]):focus,textarea:focus,select:focus{border-color:#9b4dca;outline:0}select{background:url('data:image/svg+xml;utf8,') center right no-repeat;padding-right:3.0rem}select:focus{background-image:url('data:image/svg+xml;utf8,')}select[multiple]{background:none;height:auto}textarea{min-height:6.5rem}label,legend{display:block;font-size:1.6rem;font-weight:700;margin-bottom:.5rem}fieldset{border-width:0;padding:0}input[type='checkbox'],input[type='radio']{display:inline}.label-inline{display:inline-block;font-weight:normal;margin-left:.5rem}.container{margin:0 auto;max-width:112.0rem;padding:0 2.0rem;position:relative;width:100%}.row{display:flex;flex-direction:column;padding:0;width:100%}.row.row-no-padding{padding:0}.row.row-no-padding>.column{padding:0}.row.row-wrap{flex-wrap:wrap}.row.row-top{align-items:flex-start}.row.row-bottom{align-items:flex-end}.row.row-center{align-items:center}.row.row-stretch{align-items:stretch}.row.row-baseline{align-items:baseline}.row .column{display:block;flex:1 1 auto;margin-left:0;max-width:100%;width:100%}.row .column.column-offset-10{margin-left:10%}.row .column.column-offset-20{margin-left:20%}.row .column.column-offset-25{margin-left:25%}.row .column.column-offset-33,.row .column.column-offset-34{margin-left:33.3333%}.row .column.column-offset-40{margin-left:40%}.row .column.column-offset-50{margin-left:50%}.row .column.column-offset-60{margin-left:60%}.row .column.column-offset-66,.row .column.column-offset-67{margin-left:66.6666%}.row .column.column-offset-75{margin-left:75%}.row .column.column-offset-80{margin-left:80%}.row .column.column-offset-90{margin-left:90%}.row .column.column-10{flex:0 0 10%;max-width:10%}.row .column.column-20{flex:0 0 20%;max-width:20%}.row .column.column-25{flex:0 0 25%;max-width:25%}.row .column.column-33,.row .column.column-34{flex:0 0 33.3333%;max-width:33.3333%}.row .column.column-40{flex:0 0 40%;max-width:40%}.row .column.column-50{flex:0 0 50%;max-width:50%}.row .column.column-60{flex:0 0 60%;max-width:60%}.row .column.column-66,.row .column.column-67{flex:0 0 66.6666%;max-width:66.6666%}.row .column.column-75{flex:0 0 75%;max-width:75%}.row .column.column-80{flex:0 0 80%;max-width:80%}.row .column.column-90{flex:0 0 90%;max-width:90%}.row .column .column-top{align-self:flex-start}.row .column .column-bottom{align-self:flex-end}.row .column .column-center{align-self:center}@media (min-width: 40rem){.row{flex-direction:row;margin-left:-1.0rem;width:calc(100% + 2.0rem)}.row .column{margin-bottom:inherit;padding:0 1.0rem}}a{color:#9b4dca;text-decoration:none}a:focus,a:hover{color:#606c76}dl,ol,ul{list-style:none;margin-top:0;padding-left:0}dl dl,dl ol,dl ul,ol dl,ol ol,ol ul,ul dl,ul ol,ul ul{font-size:90%;margin:1.5rem 0 1.5rem 3.0rem}ol{list-style:decimal inside}ul{list-style:circle inside}.button,button,dd,dt,li{margin-bottom:1.0rem}fieldset,input,select,textarea{margin-bottom:1.5rem}blockquote,dl,figure,form,ol,p,pre,table,ul{margin-bottom:2.5rem}table{border-spacing:0;display:block;overflow-x:auto;text-align:left;width:100%}td,th{border-bottom:0.1rem solid #e1e1e1;padding:1.2rem 1.5rem}td:first-child,th:first-child{padding-left:0}td:last-child,th:last-child{padding-right:0}@media (min-width: 40rem){table{display:table;overflow-x:initial}}b,strong{font-weight:bold}p{margin-top:0}h1,h2,h3,h4,h5,h6{font-weight:300;letter-spacing:-.1rem;margin-bottom:2.0rem;margin-top:0}h1{font-size:4.6rem;line-height:1.2}h2{font-size:3.6rem;line-height:1.25}h3{font-size:2.8rem;line-height:1.3}h4{font-size:2.2rem;letter-spacing:-.08rem;line-height:1.35}h5{font-size:1.8rem;letter-spacing:-.05rem;line-height:1.5}h6{font-size:1.6rem;letter-spacing:0;line-height:1.4}img{max-width:100%}.clearfix:after{clear:both;content:' ';display:table}.float-left{float:left}.float-right{float:right}\n"]} \ No newline at end of file diff --git a/Public/styles/normalize.css b/Public/styles/normalize.css new file mode 100644 index 0000000..192eb9c --- /dev/null +++ b/Public/styles/normalize.css @@ -0,0 +1,349 @@ +/*! normalize.css v8.0.1 | MIT License | github.com/necolas/normalize.css */ + +/* Document + ========================================================================== */ + +/** + * 1. Correct the line height in all browsers. + * 2. Prevent adjustments of font size after orientation changes in iOS. + */ + +html { + line-height: 1.15; /* 1 */ + -webkit-text-size-adjust: 100%; /* 2 */ +} + +/* Sections + ========================================================================== */ + +/** + * Remove the margin in all browsers. + */ + +body { + margin: 0; +} + +/** + * Render the `main` element consistently in IE. + */ + +main { + display: block; +} + +/** + * Correct the font size and margin on `h1` elements within `section` and + * `article` contexts in Chrome, Firefox, and Safari. + */ + +h1 { + font-size: 2em; + margin: 0.67em 0; +} + +/* Grouping content + ========================================================================== */ + +/** + * 1. Add the correct box sizing in Firefox. + * 2. Show the overflow in Edge and IE. + */ + +hr { + box-sizing: content-box; /* 1 */ + height: 0; /* 1 */ + overflow: visible; /* 2 */ +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +pre { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/* Text-level semantics + ========================================================================== */ + +/** + * Remove the gray background on active links in IE 10. + */ + +a { + background-color: transparent; +} + +/** + * 1. Remove the bottom border in Chrome 57- + * 2. Add the correct text decoration in Chrome, Edge, IE, Opera, and Safari. + */ + +abbr[title] { + border-bottom: none; /* 1 */ + text-decoration: underline; /* 2 */ + text-decoration: underline dotted; /* 2 */ +} + +/** + * Add the correct font weight in Chrome, Edge, and Safari. + */ + +b, +strong { + font-weight: bolder; +} + +/** + * 1. Correct the inheritance and scaling of font size in all browsers. + * 2. Correct the odd `em` font sizing in all browsers. + */ + +code, +kbd, +samp { + font-family: monospace, monospace; /* 1 */ + font-size: 1em; /* 2 */ +} + +/** + * Add the correct font size in all browsers. + */ + +small { + font-size: 80%; +} + +/** + * Prevent `sub` and `sup` elements from affecting the line height in + * all browsers. + */ + +sub, +sup { + font-size: 75%; + line-height: 0; + position: relative; + vertical-align: baseline; +} + +sub { + bottom: -0.25em; +} + +sup { + top: -0.5em; +} + +/* Embedded content + ========================================================================== */ + +/** + * Remove the border on images inside links in IE 10. + */ + +img { + border-style: none; +} + +/* Forms + ========================================================================== */ + +/** + * 1. Change the font styles in all browsers. + * 2. Remove the margin in Firefox and Safari. + */ + +button, +input, +optgroup, +select, +textarea { + font-family: inherit; /* 1 */ + font-size: 100%; /* 1 */ + line-height: 1.15; /* 1 */ + margin: 0; /* 2 */ +} + +/** + * Show the overflow in IE. + * 1. Show the overflow in Edge. + */ + +button, +input { /* 1 */ + overflow: visible; +} + +/** + * Remove the inheritance of text transform in Edge, Firefox, and IE. + * 1. Remove the inheritance of text transform in Firefox. + */ + +button, +select { /* 1 */ + text-transform: none; +} + +/** + * Correct the inability to style clickable types in iOS and Safari. + */ + +button, +[type="button"], +[type="reset"], +[type="submit"] { + -webkit-appearance: button; +} + +/** + * Remove the inner border and padding in Firefox. + */ + +button::-moz-focus-inner, +[type="button"]::-moz-focus-inner, +[type="reset"]::-moz-focus-inner, +[type="submit"]::-moz-focus-inner { + border-style: none; + padding: 0; +} + +/** + * Restore the focus styles unset by the previous rule. + */ + +button:-moz-focusring, +[type="button"]:-moz-focusring, +[type="reset"]:-moz-focusring, +[type="submit"]:-moz-focusring { + outline: 1px dotted ButtonText; +} + +/** + * Correct the padding in Firefox. + */ + +fieldset { + padding: 0.35em 0.75em 0.625em; +} + +/** + * 1. Correct the text wrapping in Edge and IE. + * 2. Correct the color inheritance from `fieldset` elements in IE. + * 3. Remove the padding so developers are not caught out when they zero out + * `fieldset` elements in all browsers. + */ + +legend { + box-sizing: border-box; /* 1 */ + color: inherit; /* 2 */ + display: table; /* 1 */ + max-width: 100%; /* 1 */ + padding: 0; /* 3 */ + white-space: normal; /* 1 */ +} + +/** + * Add the correct vertical alignment in Chrome, Firefox, and Opera. + */ + +progress { + vertical-align: baseline; +} + +/** + * Remove the default vertical scrollbar in IE 10+. + */ + +textarea { + overflow: auto; +} + +/** + * 1. Add the correct box sizing in IE 10. + * 2. Remove the padding in IE 10. + */ + +[type="checkbox"], +[type="radio"] { + box-sizing: border-box; /* 1 */ + padding: 0; /* 2 */ +} + +/** + * Correct the cursor style of increment and decrement buttons in Chrome. + */ + +[type="number"]::-webkit-inner-spin-button, +[type="number"]::-webkit-outer-spin-button { + height: auto; +} + +/** + * 1. Correct the odd appearance in Chrome and Safari. + * 2. Correct the outline style in Safari. + */ + +[type="search"] { + -webkit-appearance: textfield; /* 1 */ + outline-offset: -2px; /* 2 */ +} + +/** + * Remove the inner padding in Chrome and Safari on macOS. + */ + +[type="search"]::-webkit-search-decoration { + -webkit-appearance: none; +} + +/** + * 1. Correct the inability to style clickable types in iOS and Safari. + * 2. Change font properties to `inherit` in Safari. + */ + +::-webkit-file-upload-button { + -webkit-appearance: button; /* 1 */ + font: inherit; /* 2 */ +} + +/* Interactive + ========================================================================== */ + +/* + * Add the correct display in Edge, IE 10+, and Firefox. + */ + +details { + display: block; +} + +/* + * Add the correct display in all browsers. + */ + +summary { + display: list-item; +} + +/* Misc + ========================================================================== */ + +/** + * Add the correct display in IE 10+. + */ + +template { + display: none; +} + +/** + * Add the correct display in IE 10. + */ + +[hidden] { + display: none; +} diff --git a/Public/styles/style.css b/Public/styles/style.css new file mode 100644 index 0000000..c2da572 --- /dev/null +++ b/Public/styles/style.css @@ -0,0 +1,133 @@ +html, body { + margin: 10px; + font-family: 'Catamaran', sans-serif; +} +header > nav { + display: block +} +header > nav > ul { + list-style: none; +} +header h1 { + margin: 20px 0px; +} +header .logo { + height: 0.9em; +} +header > nav > ul > li { + float: left; + margin: 5px; + padding: 5px; +} +header > nav > ul:first-child > li:first-child { + margin-left: -5px; +} +header > nav > ul.float-right li { + float: right; +} +header.container { + margin-bottom: 20px; +} +ul.articles { + list-style: none; +} + +ul.articles > li { + padding: 1em 0px; +} + +ul.articles li > .title i.el { + margin-right: 4px; +} + +ul.articles li > .summary { + padding: 8px 0px; +} + +ul.articles li > .publishedAt, ul.articles li > .author{ + font-size: 0.8em; +} + +ul.articles li > .author a.twitter-handle{ + line-height: 2em; + height: 2em; + padding: 0px 5px; + text-transform: unset; + margin-left: 4px; +} + +main nav.posts-filter ul { + list-style: none +} + +main nav.posts-filter ul li{ + float: left; + list-style: none +} + +main nav.posts-filter ul li a.button{ + padding: 0px 10px; + line-height: 2.25em; + height: 2.25em; + margin-right: 1em; +} + +ul.articles li > .social-share > ul{ + list-style: none; + display: inline-block; + margin: 5px; +} + +ul.articles li > .social-share > ul li{ + display: inline-block; + padding: 0; +} + +ul.articles li > .social-share > ul > li > a{ + line-height: 2em; + height: 2em; + padding: 0px 5px; +} + +ul.articles li > ul.podcast-players { + list-style: none; + margin: 0; +} + +ul.articles li > ul.podcast-players > li { + display: inline-block; + margin-left: 4px; +} + +ul.articles li > ul.podcast-players > li > a { + border: solid 1px; + height: 3em; + display: inline-block; + padding: 4px; + border-radius: 8px; + width: 10em; +} + +ul.articles li > ul.podcast-players > li img { + object-fit: contain; + height: 100%; +} + +ul.articles li > ul.podcast-players > li img+div { + display: inline-block; + margin-left: 4px; +} + +ul.articles li > ul.podcast-players > li img+div > div:first-child { + font-size: 0.7em; +} +/* +ul.articles li > ul.podcast-players > li { + height: 2em; + display: inline-block; +} + + + + +*/ diff --git a/Public/test.html b/Public/test.html new file mode 100644 index 0000000..6f7317f --- /dev/null +++ b/Public/test.html @@ -0,0 +1,804 @@ + + + + OrchardNest - Swift Articles and News + + + + + + + + + + + + + + + + + +
+ +
+

+  OrchardNest +

+
+
+

Swift Articles and News

+
+
+
+ +
+ + +
+
+ + \ No newline at end of file diff --git a/Resources/Views/about.md b/Resources/Views/about.md new file mode 100644 index 0000000..110783b --- /dev/null +++ b/Resources/Views/about.md @@ -0,0 +1,3 @@ +# About + +Coming Soon... diff --git a/Resources/Views/support.md b/Resources/Views/support.md new file mode 100644 index 0000000..4f92fbe --- /dev/null +++ b/Resources/Views/support.md @@ -0,0 +1,3 @@ +# Support + +Coming Soon... diff --git a/Scripts/script.sh b/Scripts/script.sh index 6738752..8915708 100644 --- a/Scripts/script.sh +++ b/Scripts/script.sh @@ -1,7 +1,7 @@ #!/bin/bash if [[ $TRAVIS_OS_NAME = 'osx' ]]; then - swiftformat --lint . && swiftlint + swift run swiftformat --lint . && swift run swiftlint elif [[ $TRAVIS_OS_NAME = 'linux' ]]; then # What to do in Ubunutu RELEASE_DOT=$(lsb_release -sr) diff --git a/Sources/OrchardNestKit/BlogReader.swift b/Sources/OrchardNestKit/BlogReader.swift deleted file mode 100644 index 19f7dfa..0000000 --- a/Sources/OrchardNestKit/BlogReader.swift +++ /dev/null @@ -1,11 +0,0 @@ -import Foundation - -public class BlogReader { - public init() {} - - public func sites(fromURL url: URL) throws -> [LanguageContent] { - let decoder = JSONDecoder() - let data = try Data(contentsOf: url) - return try decoder.decode([LanguageContent].self, from: data) - } -} diff --git a/Sources/OrchardNestKit/EntryChannel.swift b/Sources/OrchardNestKit/EntryChannel.swift new file mode 100644 index 0000000..ee5c204 --- /dev/null +++ b/Sources/OrchardNestKit/EntryChannel.swift @@ -0,0 +1,29 @@ +import Foundation + +public struct EntryChannel: Codable { + public let id: UUID + public let title: String + public let author: String + public let siteURL: URL + public let twitterHandle: String? + public let imageURL: URL? + public let podcastAppleId: Int? + + public init( + id: UUID, + title: String, + siteURL: URL, + author: String, + twitterHandle: String?, + imageURL: URL?, + podcastAppleId: Int? + ) { + self.id = id + self.title = title + self.siteURL = siteURL + self.author = author + self.twitterHandle = twitterHandle + self.imageURL = imageURL + self.podcastAppleId = podcastAppleId + } +} diff --git a/Sources/OrchardNestKit/EntryItem.swift b/Sources/OrchardNestKit/EntryItem.swift new file mode 100644 index 0000000..26400c5 --- /dev/null +++ b/Sources/OrchardNestKit/EntryItem.swift @@ -0,0 +1,155 @@ +import Foundation + +struct IncompleteCategoryType: Error { + let type: EntryCategoryType +} + +public enum EntryCategoryType: String, Codable { + case companies + case design + case development + case marketing + case newsletters + case podcasts + case updates + case youtube +} + +struct EntryCategoryCodable: Codable { + let type: EntryCategoryType + let value: String? +} + +public enum EntryCategory: Codable { + public init(podcastEpisodeAtURL url: URL) { + self = .podcasts(url) + } + + public init(youtubeVideoWithID id: String) { + self = .youtube(id) + } + + public init(type: EntryCategoryType) throws { + switch type { + case .companies: self = .companies + case .design: self = .design + case .development: self = .development + case .marketing: self = .marketing + case .newsletters: self = .newsletters + case .updates: self = .updates + default: + throw IncompleteCategoryType(type: type) + } + } + + public init(from decoder: Decoder) throws { + let codable = try EntryCategoryCodable(from: decoder) + + switch codable.type { + case .companies: self = .companies + case .design: self = .design + case .development: self = .development + case .marketing: self = .marketing + case .newsletters: self = .newsletters + case .updates: self = .updates + case .podcasts: + guard let url = codable.value.flatMap(URL.init(string:)) else { + throw DecodingError.valueNotFound(URL.self, DecodingError.Context(codingPath: [], debugDescription: "")) + } + self = .podcasts(url) + case .youtube: + guard let id = codable.value else { + throw DecodingError.valueNotFound(URL.self, DecodingError.Context(codingPath: [], debugDescription: "")) + } + self = .youtube(id) + } + } + + public func encode(to encoder: Encoder) throws { + let codable = EntryCategoryCodable(type: type, value: value) + try codable.encode(to: encoder) + } + + case companies + case design + case development + case marketing + case newsletters + case podcasts(URL) + case updates + case youtube(String) + + public var type: EntryCategoryType { + switch self { + case .companies: return .companies + case .design: return .design + case .development: return .development + case .marketing: return .marketing + case .newsletters: return .newsletters + case .podcasts: return .podcasts + case .updates: return .updates + case .youtube: return .youtube + } + } + + var value: String? { + switch self { + case let .podcasts(url): return url.absoluteString + case let .youtube(id): return id + default: return nil + } + } +} + +public struct EntryItem: Codable { + public let id: UUID + public let channel: EntryChannel + public let feedId: String + public let title: String + public let summary: String + public let url: URL + public let imageURL: URL? + public let publishedAt: Date + public let category: EntryCategory + + public init(id: UUID, + channel: EntryChannel, + category: EntryCategory, + feedId: String, + title: String, + summary: String, + url: URL, + imageURL: URL?, + publishedAt: Date) { + self.id = id + self.channel = channel + self.feedId = feedId + self.title = title + self.summary = summary + self.url = url + self.imageURL = imageURL + self.category = category + self.publishedAt = publishedAt + } +} + +public extension EntryItem { + var podcastEpisodeURL: URL? { + if case let .podcasts(url) = category { + return url + } + return nil + } + + var youtubeID: String? { + if case let .youtube(id) = category { + return id + } + return nil + } + + var twitterShareLink: String { + let text = title + (channel.twitterHandle.map { " from @\($0)" } ?? "") + return "https://twitter.com/intent/tweet?text=\(text)&via=orchardnest&url=\(url)" + } +} diff --git a/Sources/OrchardNestKit/Channel.swift b/Sources/OrchardNestKit/FeedChannel.swift similarity index 77% rename from Sources/OrchardNestKit/Channel.swift rename to Sources/OrchardNestKit/FeedChannel.swift index dfb6ddf..2145f4c 100644 --- a/Sources/OrchardNestKit/Channel.swift +++ b/Sources/OrchardNestKit/FeedChannel.swift @@ -1,7 +1,16 @@ import FeedKit import Foundation -public struct Channel: Codable { +extension URL { + func ensureAbsolute(_ baseURL: URL) -> URL { + guard host == nil else { + return self + } + return URL(string: relativeString, relativeTo: baseURL) ?? self + } +} + +public struct FeedChannel: Codable { static let youtubeImgBaseURL = URL(string: "https://img.youtube.com/vi/")! public static func imageURL(fromYoutubeId ytId: String) -> URL { return youtubeImgBaseURL.appendingPathComponent(ytId).appendingPathComponent("hqdefault.jpg") @@ -18,13 +27,14 @@ public struct Channel: Codable { public let ytId: String? public let language: String public let category: String - public let items: [Item] + public let items: [FeedItem] public let itemCount: Int? // swiftlint:disable:next function_body_length - public init(language: String, category: String, site: Site) throws { - let parser = FeedParser(URL: site.feed_url) + public init(language: String, category: String, site: Site, data: Data) throws { + let parser = FeedParser(data: data) let feed = try parser.parse().get() + switch feed { case let .json(json): title = json.title ?? site.title @@ -41,19 +51,21 @@ public struct Channel: Codable { self.category = category itemCount = json.items?.count - items = json.items?.compactMap { (item) -> Item? in + items = json.items?.compactMap { (item) -> FeedItem? in let siteUrl: URL = site.site_url guard let title = item.title, let summary = item.summary, let url = item.externalUrl.flatMap(URL.init(string:)) ?? item.url.flatMap(URL.init(string:)), - let id = item.id ?? item.url ?? item.externalUrl else { + let id = item.id ?? item.url ?? item.externalUrl, + let published = item.datePublished ?? item.dateModified + else { return nil } let content = item.contentHtml ?? item.contentText let image = item.image.flatMap(URL.init(string:)) ?? item.bannerImage.flatMap(URL.init(string:)) - let published = item.datePublished ?? item.dateModified ?? Date() - return Item( + + return FeedItem( siteUrl: siteUrl, id: id, title: title, @@ -65,7 +77,7 @@ public struct Channel: Codable { audio: nil, published: published ) - } ?? [Item]() + } ?? [FeedItem]() case let .rss(rss): title = rss.title ?? site.title @@ -74,7 +86,7 @@ public struct Channel: Codable { siteUrl = rss.link.flatMap(URL.init(string:)) ?? site.site_url feedUrl = site.feed_url twitterHandle = site.twitter_url?.lastPathComponent - image = rss.image?.url.flatMap(URL.init(string:)) + image = rss.image?.url.flatMap(URL.init(string:)) ?? rss.iTunes?.iTunesImage?.attributes?.href.flatMap(URL.init(string:)) // self.image = atom.image updated = rss.pubDate ?? Date() self.language = language @@ -82,7 +94,7 @@ public struct Channel: Codable { ytId = nil itemCount = rss.items?.count - items = rss.items?.compactMap { (item) -> Item? in + items = rss.items?.compactMap { (item) -> FeedItem? in let siteUrl: URL = site.site_url guard let title = item.title, @@ -90,9 +102,12 @@ public struct Channel: Codable { item.content?.contentEncoded ?? item.media?.mediaDescription?.value, let id = item.guid?.value ?? item.link, - let url = item.link.flatMap(URL.init(string:)) else { + let itemUrl = item.link.flatMap(URL.init(string:)), + let published = item.pubDate ?? item.dublinCore?.dcDate + else { return nil } + let url = itemUrl.ensureAbsolute(siteUrl) let enclosure = item.enclosure.flatMap(Enclosure.init) let content = item.content?.contentEncoded let image = item.iTunes?.iTunesImage?.attributes?.href.flatMap(URL.init(string:)) ?? @@ -102,8 +117,8 @@ public struct Channel: Codable { }.first // let ytId: String // let itId = item.media. - let published = item.pubDate ?? Date() - return Item( + + return FeedItem( siteUrl: siteUrl, id: id, title: title, @@ -115,7 +130,7 @@ public struct Channel: Codable { audio: enclosure?.audioURL, published: published ) - } ?? [Item]() + } ?? [FeedItem]() case let .atom(atom): title = atom.title ?? site.title @@ -140,7 +155,7 @@ public struct Channel: Codable { URL(string: $0, relativeTo: site.feed_url) } ?? ytId.map(Self.imageURL) self.ytId = ytId - items = atom.entries?.compactMap { (entry) -> Item? in + items = atom.entries?.compactMap { (entry) -> FeedItem? in let siteUrl: URL = site.site_url let media = entry.links?.compactMap(Enclosure.init(element:)) guard let title = entry.title else { @@ -148,22 +163,28 @@ public struct Channel: Codable { } guard let summary = entry.summary?.value ?? entry.content?.value ?? - entry.media?.mediaGroup?.mediaDescription?.value else { + entry.media?.mediaGroup?.mediaDescription?.value + else { return nil } - guard let url: URL = entry.links?.first?.attributes?.href.flatMap(URL.init(string:)) else { + guard let entryUrl: URL = entry.links?.first?.attributes?.href.flatMap(URL.init(string:)) else { return nil } guard let id = entry.id else { return nil } + + guard let published = entry.published else { + return nil + } let ytId: String? if id.starts(with: "yt:video:") { ytId = id.components(separatedBy: ":").last } else { ytId = nil } - return Item( + let url = entryUrl.ensureAbsolute(siteUrl) + return FeedItem( siteUrl: siteUrl, id: id, title: title, @@ -173,9 +194,9 @@ public struct Channel: Codable { image: media?.compactMap { $0.imageURL }.first, ytId: ytId, audio: media?.compactMap { $0.audioURL }.first, - published: entry.published ?? Date() + published: published ) - } ?? [Item]() + } ?? [FeedItem]() } } } diff --git a/Sources/OrchardNestKit/Item.swift b/Sources/OrchardNestKit/FeedItem.swift similarity index 89% rename from Sources/OrchardNestKit/Item.swift rename to Sources/OrchardNestKit/FeedItem.swift index f0de376..0864bd2 100644 --- a/Sources/OrchardNestKit/Item.swift +++ b/Sources/OrchardNestKit/FeedItem.swift @@ -1,6 +1,6 @@ import Foundation -public struct Item: Codable { +public struct FeedItem: Codable { public let siteUrl: URL public let id: String public let title: String diff --git a/Sources/OrchardNestServer/Configurator.swift b/Sources/OrchardNestServer/Configurator.swift new file mode 100644 index 0000000..1623fbc --- /dev/null +++ b/Sources/OrchardNestServer/Configurator.swift @@ -0,0 +1,152 @@ +import Fluent +import FluentPostgresDriver +import Ink +import OrchardNestKit +import Plot +import QueuesFluentDriver +import Vapor +extension Date { + func get(_ type: Calendar.Component) -> Int { + let calendar = Calendar.current + return calendar.component(type, from: self) + } +} + +extension HTML: ResponseEncodable { + public func encodeResponse(for request: Request) -> EventLoopFuture { + var headers = HTTPHeaders() + headers.add(name: .contentType, value: "text/html") + return request.eventLoop.makeSucceededFuture(.init( + status: .ok, headers: headers, body: .init(string: render()) + )) + } +} + +struct OrganizedSite { + let languageCode: String + let categorySlug: String + let site: Site +} + +// +public final class Configurator: ConfiguratorProtocol { + public static let shared: ConfiguratorProtocol = Configurator() + + // + ///// Called before your application initializes. + public func configure(_ app: Application) throws { + // Register providers first + // try services.register(FluentPostgreSQLProvider()) + // try services.register(AuthenticationProvider()) + + // services.register(DirectoryIndexMiddleware.self) + + // Register middleware + // var middlewares = MiddlewareConfig() // Create _empty_ middleware config + // middlewares.use(SessionsMiddleware.self) // Enables sessions. + // let rootPath = Environment.get("ROOT_PATH") ?? app.directory.publicDirectory + +// app.webSockets = WebSocketRepository() +// +// app.middleware.use(DirectoryIndexMiddleware(publicDirectory: rootPath)) + + app.middleware.use(FileMiddleware(publicDirectory: app.directory.publicDirectory)) + +// // Configure Leaf +// app.views.use(.leaf) +// app.leaf.cache.isEnabled = app.environment.isRelease +// app.middleware.use(ErrorMiddleware.default(environment: app.environment)) + // middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response + // services.register(middlewares) + + // Configure a SQLite database + let postgreSQLConfig: PostgresConfiguration + + if let url = Environment.get("DATABASE_URL") { + postgreSQLConfig = PostgresConfiguration(url: url)! + } else { + postgreSQLConfig = PostgresConfiguration(hostname: "localhost", username: "orchardnest") + } + + app.databases.use(.postgres(configuration: postgreSQLConfig, maxConnectionsPerEventLoop: 8, connectionPoolTimeout: .seconds(60)), as: .psql) + app.migrations.add([ + CategoryMigration(), + LanguageMigration(), + CategoryTitleMigration(), + ChannelMigration(), + EntryMigration(), + PodcastEpisodeMigration(), + YouTubeChannelMigration(), + YouTubeVideoMigration(), + PodcastChannelMigration(), + ChannelStatusMigration(), + LatestEntriesMigration(), + JobModelMigrate(schema: "queue_jobs") + ]) + + app.queues.configuration.refreshInterval = .seconds(25) + app.queues.use(.fluent()) +// app.databases.middleware.use(UserEmailerMiddleware(app: app)) +// +// app.migrations.add(CreateDevice()) +// app.migrations.add(CreateAppleUser()) +// app.migrations.add(CreateDeviceWorkout()) +// app.migrations.add(ActivateWorkout()) + // let wss = NIOWebSocketServer.default() + +// app.webSocket("api", "v1", "workouts", ":id", "listen") { req, websocket in +// guard let idData = try? Base32CrockfordEncoding.encoding.decode(base32Encoded: req.parameters.get("id")!) else { +// return +// } +// let workoutID = UUID(data: idData) +// +// _ = Workout.find(workoutID, on: req.db).unwrap(or: Abort(HTTPResponseStatus.notFound)).flatMapThrowing { workout in +// let workoutId = try workout.requireID() +// app.webSockets.save(websocket, withID: workoutId) +// } +// } + + app.queues.add(RefreshJob()) + app.queues.schedule(RefreshJob()).daily().at(.midnight) + app.queues.schedule(RefreshJob()).daily().at(7, 30) + app.queues.schedule(RefreshJob()).daily().at(19, 30) + #if DEBUG + if !app.environment.isRelease { + let minute = Date().get(.minute) + [0, 30].map { ($0 + minute + 5).remainderReportingOverflow(dividingBy: 60).partialValue }.forEach { minute in + app.queues.schedule(RefreshJob()).hourly().at(.init(integerLiteral: minute)) + } + } + #endif + try app.queues.startInProcessJobs(on: .default) + app.commands.use(RefreshCommand(help: "Imports data into the database"), as: "refresh") + + try app.autoMigrate().wait() + // services.register(wss, as: WebSocketServer.self) + + let api = app.grouped("api", "v1") + + let markdownDirectory = app.directory.viewsDirectory + let parser = MarkdownParser() + + let textPairs = FileManager.default.enumerator(atPath: markdownDirectory)?.compactMap { $0 as? String }.map { path in + URL(fileURLWithPath: app.directory.viewsDirectory + path) + }.compactMap { url in + (try? String(contentsOf: url)).map { (url.deletingPathExtension().lastPathComponent, $0) } + } + + let pages = textPairs.map(Dictionary.init(uniqueKeysWithValues:))?.mapValues( + parser.parse + ) + + try app.register(collection: HTMLController(views: pages)) + try api.grouped("entires").register(collection: EntryController()) + + app.post("jobs") { req in + req.queue.dispatch( + RefreshJob.self, + RefreshConfiguration() + ).map { HTTPStatus.created } + } + } +} diff --git a/Sources/OrchardNestServer/ConfiguratorProtocol.swift b/Sources/OrchardNestServer/ConfiguratorProtocol.swift new file mode 100644 index 0000000..dbd67e4 --- /dev/null +++ b/Sources/OrchardNestServer/ConfiguratorProtocol.swift @@ -0,0 +1,5 @@ +import Vapor + +public protocol ConfiguratorProtocol { + func configure(_ app: Application) throws +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/CategoryMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/CategoryMigration.swift new file mode 100644 index 0000000..d94196f --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/CategoryMigration.swift @@ -0,0 +1,14 @@ +import Fluent +import Vapor + +struct CategoryMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + database.schema(Category.schema) + .field("slug", .string, .identifier(auto: false)) + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(Category.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/CategoryTitleMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/CategoryTitleMigration.swift new file mode 100644 index 0000000..930e783 --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/CategoryTitleMigration.swift @@ -0,0 +1,18 @@ +import Fluent +import Vapor + +struct CategoryTitleMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + database.schema(CategoryTitle.schema) + .id() + .field("code", .string, .references(Language.schema, "code")) + .field("slug", .string, .references(Category.schema, "slug")) + .field("title", .string, .required) + .unique(on: "code", "slug") + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(CategoryTitle.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/ChannelMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/ChannelMigration.swift new file mode 100644 index 0000000..248d9bf --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/ChannelMigration.swift @@ -0,0 +1,27 @@ +import Fluent +import Vapor + +struct ChannelMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + database.schema(Channel.schema) + .id() + .field("language_code", .string, .references(Language.schema, "code")) + .field("category_slug", .string, .references(Category.schema, "slug")) + .field("title", .string, .required) + .field("subtitle", .string) + .field("author", .string, .required) + .field("site_url", .string, .required) + .field("feed_url", .string, .required) + .field("twitter_handle", .string) + .field("image", .string) + .field("published_at", .datetime, .required) + .field("created_at", .datetime, .required) + .field("updated_at", .datetime, .required) + .unique(on: "feed_url") + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(Channel.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/ChannelStatusMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/ChannelStatusMigration.swift new file mode 100644 index 0000000..7215659 --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/ChannelStatusMigration.swift @@ -0,0 +1,22 @@ +import Fluent +import Vapor + +struct ChannelStatusMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + var channelStatusType = database.enum("channel_status_type") + for type in ChannelStatusType.allCases { + channelStatusType = channelStatusType.case(type.rawValue) + } + return channelStatusType.create().flatMap { channelStatusType in + + database.schema(ChannelStatus.schema) + .field("feed_url", .string, .identifier(auto: false)) + .field("status", channelStatusType, .required) + .create() + } + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(ChannelStatus.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/EntryMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/EntryMigration.swift new file mode 100644 index 0000000..3788219 --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/EntryMigration.swift @@ -0,0 +1,24 @@ +import Fluent +import Vapor + +struct EntryMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + database.schema(Entry.schema) + .id() + .field("channel_id", .uuid, .required) + .field("feed_id", .string, .required) + .field("title", .string, .required) + .field("summary", .string, .required) + .field("content", .string) + .field("url", .string, .required) + .field("image", .string) + .field("published_at", .datetime, .required) + .field("created_at", .datetime, .required) + .field("updated_at", .datetime, .required) + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(Entry.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/LanguageMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/LanguageMigration.swift new file mode 100644 index 0000000..5c462e3 --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/LanguageMigration.swift @@ -0,0 +1,15 @@ +import Fluent +import Vapor + +struct LanguageMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + database.schema(Language.schema) + .field("code", .string, .identifier(auto: false)) + .field("title", .string, .required) + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(Language.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/LatestEntriesMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/LatestEntriesMigration.swift new file mode 100644 index 0000000..3852f7a --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/LatestEntriesMigration.swift @@ -0,0 +1,43 @@ +import FluentSQL +import Vapor + +struct LatestEntriesMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + guard let sql = database as? SQLDatabase else { + return database.eventLoop.makeFailedFuture(InvalidDatabaseError()) + } + + return sql.raw(""" + -- DDL generated by Postico 1.5.14 + -- Not all database features are supported. Do not use for backup. + + -- Table Definition ---------------------------------------------- + + CREATE VIEW latest_entries AS SELECT latest.id, + latest.channel_id + FROM ( SELECT DISTINCT ON (entries.channel_id) entries.id, + entries.channel_id, + entries.feed_id, + entries.title, + entries.summary, + entries.content, + entries.url, + entries.image, + entries.published_at, + entries.created_at, + entries.updated_at + FROM entries + ORDER BY entries.channel_id, entries.published_at DESC) latest + ORDER BY latest.published_at DESC; + + """ + ).run() + } + + func revert(on database: Database) -> EventLoopFuture { + guard let sql = database as? SQLDatabase else { + return database.eventLoop.makeFailedFuture(InvalidDatabaseError()) + } + return sql.raw("drop view if exists latest_entries").run() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/PodcastChannelMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/PodcastChannelMigration.swift new file mode 100644 index 0000000..9c2e3f2 --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/PodcastChannelMigration.swift @@ -0,0 +1,16 @@ +import Fluent +import Vapor + +struct PodcastChannelMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + database.schema(PodcastChannel.schema) + .field("channel_id", .uuid, .identifier(auto: false), .references(Channel.schema, .id)) + .field("apple_id", .int, .required) + .unique(on: "apple_id") + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(PodcastChannel.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/PodcastEpisodeMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/PodcastEpisodeMigration.swift new file mode 100644 index 0000000..ec4aa2e --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/PodcastEpisodeMigration.swift @@ -0,0 +1,15 @@ +import Fluent +import Vapor + +struct PodcastEpisodeMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + database.schema(PodcastEpisode.schema) + .field("entry_id", .uuid, .identifier(auto: false), .references(Entry.schema, .id)) + .field("audio", .string, .required) + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(PodcastEpisode.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/YouTubeChannelMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/YouTubeChannelMigration.swift new file mode 100644 index 0000000..5e7eda2 --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/YouTubeChannelMigration.swift @@ -0,0 +1,16 @@ +import Fluent +import Vapor + +struct YouTubeChannelMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + database.schema(YouTubeChannel.schema) + .field("channel_id", .uuid, .identifier(auto: false), .references(Channel.schema, .id)) + .field("youtube_id", .string, .required) + .unique(on: "youtube_id") + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(YouTubeChannel.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/DB/Migration/YouTubeVideoMigration.swift b/Sources/OrchardNestServer/Controllers/DB/Migration/YouTubeVideoMigration.swift new file mode 100644 index 0000000..1be46c0 --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/DB/Migration/YouTubeVideoMigration.swift @@ -0,0 +1,16 @@ +import Fluent +import Vapor + +struct YouTubeVideoMigration: Migration { + func prepare(on database: Database) -> EventLoopFuture { + database.schema(YoutubeVideo.schema) + .field("entry_id", .uuid, .identifier(auto: false), .references(Entry.schema, .id)) + .field("youtube_id", .string, .required) + .unique(on: "youtube_id") + .create() + } + + func revert(on database: Database) -> EventLoopFuture { + database.schema(YoutubeVideo.schema).delete() + } +} diff --git a/Sources/OrchardNestServer/Controllers/Routing/EntryController.swift b/Sources/OrchardNestServer/Controllers/Routing/EntryController.swift new file mode 100644 index 0000000..a1e6a14 --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/Routing/EntryController.swift @@ -0,0 +1,80 @@ +import Fluent +import OrchardNestKit +import Vapor + +struct InvalidURLFormat: Error {} + +extension String { + func asURL() throws -> URL { + guard let url = URL(string: self) else { + throw InvalidURLFormat() + } + return url + } +} + +extension Entry { + func category() throws -> EntryCategory { + guard let category = EntryCategoryType(rawValue: channel.$category.id) else { + return .development + } + + if let url = podcastEpisode.flatMap({ URL(string: $0.audioURL) }) { + return .podcasts(url) + } else if let youtubeID = youtubeVideo?.youtubeId { + return .youtube(youtubeID) + } else { + return try EntryCategory(type: category) + } + } +} + +extension EntryChannel { + init(channel: Channel) throws { + try self.init( + id: channel.requireID(), + title: channel.title, + siteURL: channel.siteUrl.asURL(), + author: channel.author, + twitterHandle: channel.twitterHandle, + imageURL: channel.imageURL?.asURL(), + podcastAppleId: channel.$podcasts.value?.first?.appleId + ) + } +} + +extension EntryItem { + init(entry: Entry) throws { + try self.init( + id: entry.requireID(), + channel: EntryChannel(channel: entry.channel), + category: entry.category(), + feedId: entry.feedId, + title: entry.title, + summary: entry.summary, + url: entry.url.asURL(), + imageURL: entry.imageURL?.asURL(), + publishedAt: entry.publishedAt + ) + } +} + +struct EntryController { + func list(req: Request) -> EventLoopFuture> { + return Entry.query(on: req.db) + .sort(\.$publishedAt, .descending) + .with(\.$channel) + .paginate(for: req) + .flatMapThrowing { (page: Page) -> Page in + try page.map { (entry: Entry) -> EntryItem in + try EntryItem(entry: entry) + } + } + } +} + +extension EntryController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + routes.get("", use: list) + } +} diff --git a/Sources/OrchardNestServer/Controllers/Routing/HTMLController.swift b/Sources/OrchardNestServer/Controllers/Routing/HTMLController.swift new file mode 100644 index 0000000..def5389 --- /dev/null +++ b/Sources/OrchardNestServer/Controllers/Routing/HTMLController.swift @@ -0,0 +1,429 @@ +import Fluent +import FluentSQL +import Ink +import OrchardNestKit +import Plot +import Vapor + +struct InvalidDatabaseError: Error {} + +extension Node where Context == HTML.BodyContext { + static func playerForPodcast(withAppleId appleId: Int) -> Self { + .ul( + .class("podcast-players"), + .li( + .a( + .href("https://podcasts.apple.com/podcast/id\(appleId)"), + .img( + .src("/images/podcast-players/apple/icon.svg") + ), + .div( + .div( + .text("Listen on") + ), + .div( + .class("name"), + .text("Apple Podcasts") + ) + ) + ) + ), + .li( + .a( + .href("https://overcast.fm/itunes\(appleId)"), + .img( + .src("/images/podcast-players/overcast/icon.svg") + ), + .div( + .div( + .text("Listen on") + ), + .div( + .class("name"), + .text("Overcast") + ) + ) + ) + ), + .li( + .a( + .href("https://castro.fm/itunes/\(appleId)"), + .img( + .src("/images/podcast-players/castro/icon.svg") + ), + .div( + .div( + .text("Listen on") + ), + .div( + .class("name"), + .text("Castro") + ) + ) + ) + ), + .li( + .a( + .href("https://podcasts.apple.com/podcast/id\(appleId)"), + .img( + .src("/images/podcast-players/pocketcasts/icon.svg") + ), + .div( + .div( + .text("Listen on") + ), + .div( + .class("name"), + .text("Pocket Casts") + ) + ) + ) + ) + ) + } +} + +extension Node where Context == HTML.BodyContext { + static func filters() -> Self { + .nav( + .class("posts-filter clearfix row"), + .ul( + .class("column"), + .li(.a(.class("button"), .href("/"), .i(.class("el el-calendar")), .text(" Latest"))), + .li(.a(.class("button"), .href("/category/development"), .i(.class("el el-cogs")), .text(" Development"))), + .li(.a(.class("button"), .href("/category/marketing"), .i(.class("el el-bullhorn")), .text(" Marketing"))), + .li(.a(.class("button"), .href("/category/design"), .i(.class("el el-brush")), .text(" Design"))), + .li(.a(.class("button"), .href("/category/podcasts"), .i(.class("el el-podcast")), .text(" Podcasts"))), + .li(.a(.class("button"), .href("/category/youtube"), .i(.class("el el-video")), .text(" YouTube"))), + .li(.a(.class("button"), .href("/category/newsletters"), .i(.class("el el-envelope")), .text(" Newsletters"))) + ) + ) + } +} + +extension Node where Context == HTML.BodyContext { + static func header() -> Self { + .header( + .class("container"), + .nav( + .class("row"), + .ul( + .class("column"), + .li(.a(.href("/"), .i(.class("el el-home")), .text(" Home"))), + .li(.a(.href("/about"), .i(.class("el el-info-circle")), .text(" About"))), + .li(.a(.href("/support"), .i(.class("el el-question-sign")), .text(" Support"))) + ), + .ul(.class("float-right column"), + .li(.a(.href("https://github.com/brightdigit/OrchardNest"), .i(.class("el el-github")), .text(" GitHub"))), + .li(.a(.href("https://twitter.com/OrchardNest"), .i(.class("el el-twitter")), .text(" Twitter")))) + ), + .div( + .class("row"), + .h1( + .class("column"), + .img( + .class("logo"), + .src("/images/logo.svg") + ), + .text(" OrchardNest") + ) + ), + div( + .class("row"), + .p( + .class("tagline column"), + .text("Swift Articles and News") + ) + ) + ) + } +} + +extension Node where Context == HTML.DocumentContext { + static func head(withSubtitle subtitle: String) -> Self { + return + .head( + .title("OrchardNest - \(subtitle)"), + .meta(.charset(.utf8)), + .raw(""" + + + + """), + .link(.rel(.appleTouchIcon), .sizes("180x180"), .href("/apple-touch-icon.png")), + .link(.rel(.appleTouchIcon), .type("image/png"), .sizes("32x32"), .href("/favicon-32x32.png")), + .link(.rel(.appleTouchIcon), .type("image/png"), .sizes("16x16"), .href("/favicon-16x16.png")), + .link(.rel(.manifest), .href("/site.webmanifest")), + .link(.rel(.maskIcon), .href("/safari-pinned-tab.svg"), .color("#5bbad5")), + .meta(.name("msapplication-TileColor"), .content("#2b5797")), + .meta(.name("theme-color"), .content("#ffffff")), + .link(.rel(.stylesheet), .href("/styles/elusive-icons/css/elusive-icons.min.css")), + + .link(.rel(.stylesheet), .href("/styles/normalize.css")), + + .link(.rel(.stylesheet), .href("/styles/milligram.css")), + .link(.rel(.stylesheet), .href("/styles/style.css")), + .link(.rel(.stylesheet), .href("https://fonts.googleapis.com/css2?family=Catamaran:wght@100;400;800&display=swap")) + ) + } +} + +extension Node where Context == HTML.ListContext { + static func li(forEntryItem item: EntryItem) -> Self { + return + .li( + .class("blog-post"), + + .a( + .href(item.url), + .class("title"), + .h3( + .i(.class("el el-\(item.category.elClass)")), + + .text(item.title) + ) + ), + .div( + .class("publishedAt"), + .text(item.publishedAt.description) + ), + .unwrap(item.youtubeID) { + .iframe( + .src("https://www.youtube.com/embed/" + $0), + .allow("accelerometer; autoplay; encrypted-media; gyroscope; picture-in-picture"), + .allowfullscreen(true) + ) + }, + .div( + .class("summary"), + .text(item.summary.plainTextShort) + ), + .unwrap(item.podcastEpisodeURL) { + .audio( + .controls(true), + .attribute(named: "preload", value: "metadata"), + .source( + .src($0) + ) + ) + }, + .unwrap(item.channel.podcastAppleId) { + .playerForPodcast(withAppleId: $0) + }, + .div( + .class("author"), + .text("By "), + .text(item.channel.author), + .text(" at "), + .a( + .href("/channels/" + item.channel.id.uuidString), + .text(item.channel.siteURL.host ?? item.channel.title) + ), + .unwrap(item.channel.twitterHandle) { + .a( + .href("https://twitter.com/\($0)"), + .class("button twitter-handle"), + .i(.class("el el-twitter")), + .text(" @\($0)") + ) + } + ), + .div( + .class("social-share clearfix"), + .text("Share"), + .ul( + .li( + .a( + .class("button"), + .href(item.twitterShareLink), + .i(.class("el el-twitter")), + .text(" Tweet") + ) + ) + ) + ) + ) + } +} + +extension String { + var plainTextShort: String { + var result: String + + result = trimmingCharacters(in: .whitespacesAndNewlines).replacingOccurrences(of: "<[^>]+>", with: "", options: .regularExpression, range: nil) + guard result.count > 240 else { + return result + } + return result.prefix(240).components(separatedBy: " ").dropLast().joined(separator: " ").appending("...") + } +} + +extension EntryCategoryType { + var elClass: String { + switch self { + case .companies: + return "website" + case .design: + return "brush" + case .development: + return "cogs" + case .marketing: + return "bullhorn" + case .newsletters: + return "envelope" + case .podcasts: + return "podcast" + case .updates: + return "file-new" + case .youtube: + return "video" + } + } +} + +extension EntryCategory { + var elClass: String { + return type.elClass + } +} + +struct HTMLController { + let views: [String: Markdown] + + init(views: [String: Markdown]?) { + self.views = views ?? [String: Markdown]() + } + + func category(req: Request) throws -> EventLoopFuture { + guard let category = req.parameters.get("category") else { + throw Abort(.notFound) + } + + return Entry.query(on: req.db) + .with(\.$channel) { builder in + builder.with(\.$podcasts).with(\.$youtubeChannels) + } + .join(parent: \.$channel) + .with(\.$podcastEpisodes) + .join(children: \.$podcastEpisodes, method: .left) + .with(\.$youtubeVideos) + .join(children: \.$youtubeVideos, method: .left) + .filter(Channel.self, \Channel.$category.$id == category) + .filter(Channel.self, \Channel.$language.$id == "en") + .sort(\.$publishedAt, .descending) + .limit(32) + .all() + .flatMapThrowing { (entries) -> [Entry] in + guard entries.count > 0 else { + throw Abort(.notFound) + } + return entries + } + .flatMapEachThrowing { + try EntryItem(entry: $0) + } + .map { (items) -> HTML in + HTML( + .head(withSubtitle: "Swift Articles and News"), + .body( + .header(), + .main( + .class("container"), + .filters(), + .section( + .class("row"), + .ul( + .class("articles column"), + .forEach(items) { + .li(forEntryItem: $0) + } + ) + ) + ) + ) + ) + } + } + + func page(req: Request) -> EventLoopFuture { + guard let name = req.parameters.get("page") else { + return req.eventLoop.makeFailedFuture(Abort(.notFound)) + } + + guard let view = views[name] else { + return req.eventLoop.makeFailedFuture(Abort(.notFound)) + } + + let html = HTML( + .head(withSubtitle: "Support and FAQ"), + .body( + .header(), + .main( + .class("container"), + .filters(), + .section( + .class("row"), + .raw(view.html) + ) + ) + ) + ) + + return req.eventLoop.future(html) + } + + func index(req: Request) -> EventLoopFuture { + return Entry.query(on: req.db).join(LatestEntry.self, on: \Entry.$id == \LatestEntry.$id) + .with(\.$channel) { builder in + builder.with(\.$podcasts).with(\.$youtubeChannels) + } + .join(parent: \.$channel) + .with(\.$podcastEpisodes) + .join(children: \.$podcastEpisodes, method: .left) + .with(\.$youtubeVideos) + .join(children: \.$youtubeVideos, method: .left) + .filter(Channel.self, \Channel.$category.$id != "updates") + .filter(Channel.self, \Channel.$language.$id == "en") + .sort(\.$publishedAt, .descending) + .limit(32) + .all() + .flatMapEachThrowing { + try EntryItem(entry: $0) + } + .map { (items) -> HTML in + HTML( + .head(withSubtitle: "Swift Articles and News"), + .body( + .header(), + .main( + .class("container"), + .filters(), + .section( + .class("row"), + .ul( + .class("articles column"), + .forEach(items) { + .li(forEntryItem: $0) + } + ) + ) + ) + ) + ) + } + } +} + +extension HTMLController: RouteCollection { + func boot(routes: RoutesBuilder) throws { + routes.get("", use: index) + routes.get("category", ":category", use: category) + routes.get(":page", use: page) + } +} diff --git a/Sources/OrchardNestServer/Models/ChannelFeedItemsConfiguration.swift b/Sources/OrchardNestServer/Models/ChannelFeedItemsConfiguration.swift new file mode 100644 index 0000000..69bdff9 --- /dev/null +++ b/Sources/OrchardNestServer/Models/ChannelFeedItemsConfiguration.swift @@ -0,0 +1,45 @@ +import OrchardNestKit + +struct ChannelFeedItemsConfiguration { + let channel: Channel + let youtubeId: String? + let items: [FeedItem] + + init(channels: [String: Channel], feedArgs: FeedConfiguration) { + let channel: Channel + if let oldChannel = channels[feedArgs.channel.feedUrl.absoluteString] { + channel = oldChannel + } else { + channel = Channel() + } + channel.title = feedArgs.channel.title + channel.$language.id = feedArgs.languageCode + channel.$category.id = feedArgs.categorySlug + channel.subtitle = feedArgs.channel.summary + channel.author = feedArgs.channel.author + channel.siteUrl = feedArgs.channel.siteUrl.absoluteString + channel.feedUrl = feedArgs.channel.feedUrl.absoluteString + channel.twitterHandle = feedArgs.channel.twitterHandle + channel.imageURL = feedArgs.channel.image?.absoluteString + + channel.publishedAt = feedArgs.channel.updated + + self.channel = channel + items = feedArgs.channel.items + youtubeId = feedArgs.channel.ytId + } +} + +extension ChannelFeedItemsConfiguration { + func feedItems() throws -> [FeedItemConfiguration] { + let channelId = try channel.requireID() + return items.map { FeedItemConfiguration(channelId: channelId, feedItem: $0) } + } + + var youtubeChannel: YouTubeChannel? { + guard let id = channel.id, let youtubeId = self.youtubeId else { + return nil + } + return YouTubeChannel(channelId: id, youtubeId: youtubeId) + } +} diff --git a/Sources/OrchardNestServer/Models/DB/Category.swift b/Sources/OrchardNestServer/Models/DB/Category.swift new file mode 100644 index 0000000..6ea8505 --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/Category.swift @@ -0,0 +1,29 @@ +import Fluent +import Vapor + +final class Category: Model { + static var schema = "categories" + + init() {} + + init(slug: String) { + id = slug + } + + @ID(custom: "slug", generatedBy: .user) + var id: String? +} + +extension Category { + static func from(_ slug: String, on database: Database) -> EventLoopFuture { + Category.find(slug, on: database).flatMap { (langOpt) -> EventLoopFuture in + let category: Category + if let actual = langOpt { + category = actual + } else { + category = Category(slug: slug) + } + return category.save(on: database).transform(to: category) + } + } +} diff --git a/Sources/OrchardNestServer/Models/DB/CategoryTitle.swift b/Sources/OrchardNestServer/Models/DB/CategoryTitle.swift new file mode 100644 index 0000000..3da80fc --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/CategoryTitle.swift @@ -0,0 +1,26 @@ +import Fluent +import Vapor + +final class CategoryTitle: Model { + static var schema = "category_titles" + + init() {} + + init(id: UUID? = nil, language: Language, category: Category) throws { + self.id = id + $category.id = try category.requireID() + $language.id = try language.requireID() + } + + @ID() + var id: UUID? + + @Parent(key: "code") + var language: Language + + @Parent(key: "slug") + var category: Category + + @Field(key: "title") + var title: String +} diff --git a/Sources/OrchardNestServer/Models/DB/Channel.swift b/Sources/OrchardNestServer/Models/DB/Channel.swift new file mode 100644 index 0000000..8af3112 --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/Channel.swift @@ -0,0 +1,66 @@ +import Fluent +import Vapor + +final class Channel: Model { + static var schema = "channels" + + init() {} + + @ID() + var id: UUID? + + @Field(key: "title") + var title: String + + @Parent(key: "language_code") + var language: Language + + @Parent(key: "category_slug") + var category: Category + + @OptionalField(key: "subtitle") + var subtitle: String? + + @Field(key: "author") + var author: String + + @Field(key: "site_url") + var siteUrl: String + + @Field(key: "feed_url") + var feedUrl: String + + @OptionalField(key: "twitter_handle") + var twitterHandle: String? + + @OptionalField(key: "image") + var imageURL: String? + + @Field(key: "published_at") + var publishedAt: Date + + // When this Planet was created. + @Timestamp(key: "created_at", on: .create) + var createdAt: Date? + + // When this Planet was last updated. + @Timestamp(key: "updated_at", on: .update) + var updatedAt: Date? + + @Children(for: \.$channel) + var entries: [Entry] + + @Children(for: \.$channel) + var podcasts: [PodcastChannel] + + @Children(for: \.$channel) + var youtubeChannels: [YouTubeChannel] +} + +extension Channel: Validatable { + static func validations(_ validations: inout Validations) { + validations.add("siteUrl", as: URL.self) + validations.add("feedUrl", as: URL.self) + validations.add("imageURL", as: URL.self) + } +} diff --git a/Sources/OrchardNestServer/Models/DB/ChannelStatus.swift b/Sources/OrchardNestServer/Models/DB/ChannelStatus.swift new file mode 100644 index 0000000..9557ea0 --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/ChannelStatus.swift @@ -0,0 +1,23 @@ +import Fluent +import Vapor + +enum ChannelStatusType: String, Codable, CaseIterable { + case ignore +} + +final class ChannelStatus: Model { + static var schema = "channel_statuses" + + init() {} + + init(feedUrl: URL, status: ChannelStatusType) { + id = feedUrl.absoluteString + self.status = status + } + + @ID(custom: "feed_url", generatedBy: .user) + var id: String? + + @Enum(key: "status") + var status: ChannelStatusType +} diff --git a/Sources/OrchardNestServer/Models/DB/Entry.swift b/Sources/OrchardNestServer/Models/DB/Entry.swift new file mode 100644 index 0000000..1468195 --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/Entry.swift @@ -0,0 +1,64 @@ +import Fluent +import Vapor + +final class Entry: Model, Content { + static var schema = "entries" + + init() {} + + @ID() + var id: UUID? + + @Parent(key: "channel_id") + var channel: Channel + + @Field(key: "feed_id") + var feedId: String + + @Field(key: "title") + var title: String + + @Field(key: "summary") + var summary: String + + @OptionalField(key: "content") + var content: String? + + @Field(key: "url") + var url: String + + @OptionalField(key: "image") + var imageURL: String? + + @Field(key: "published_at") + var publishedAt: Date + + // When this Planet was created. + @Timestamp(key: "created_at", on: .create) + var createdAt: Date? + + // When this Planet was last updated. + @Timestamp(key: "updated_at", on: .update) + var updatedAt: Date? + + @Children(for: \.$entry) + var podcastEpisodes: [PodcastEpisode] + + var podcastEpisode: PodcastEpisode? { + return podcastEpisodes.first + } + + @Children(for: \.$entry) + var youtubeVideos: [YoutubeVideo] + + var youtubeVideo: YoutubeVideo? { + return youtubeVideos.first + } +} + +extension Entry: Validatable { + static func validations(_ validations: inout Validations) { + validations.add("url", as: URL.self) + validations.add("imageURL", as: URL.self) + } +} diff --git a/Sources/OrchardNestServer/Models/DB/Language.swift b/Sources/OrchardNestServer/Models/DB/Language.swift new file mode 100644 index 0000000..0e97839 --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/Language.swift @@ -0,0 +1,34 @@ +import Fluent +import Vapor + +final class Language: Model { + static var schema = "languages" + + init() {} + + init(code: String, title: String) { + id = code + self.title = title + } + + @ID(custom: "code", generatedBy: .user) + var id: String? + + @Field(key: "title") + var title: String +} + +extension Language { + static func from(_ pair: (String, String), on database: Database) -> EventLoopFuture { + Language.find(pair.0, on: database).flatMap { (langOpt) -> EventLoopFuture in + let language: Language + if let actual = langOpt { + actual.title = pair.1 + language = actual + } else { + language = Language(code: pair.0, title: pair.1) + } + return language.save(on: database).transform(to: language) + } + } +} diff --git a/Sources/OrchardNestServer/Models/DB/LatestEntry.swift b/Sources/OrchardNestServer/Models/DB/LatestEntry.swift new file mode 100644 index 0000000..c281084 --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/LatestEntry.swift @@ -0,0 +1,12 @@ +import Fluent +import Vapor + +final class LatestEntry: Model { + static let schema = "latest_entries" + + @ID() + var id: UUID? + + @Field(key: "channel_id") + var channelId: UUID +} diff --git a/Sources/OrchardNestServer/Models/DB/PodcastChannel.swift b/Sources/OrchardNestServer/Models/DB/PodcastChannel.swift new file mode 100644 index 0000000..13369af --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/PodcastChannel.swift @@ -0,0 +1,43 @@ +import Fluent +import Vapor + +final class PodcastChannel: Model { + static var schema = "podcast_channels" + + init() {} + + init(channelId: UUID, appleId: Int) { + id = channelId + self.appleId = appleId + } + + @ID(custom: "channel_id", generatedBy: .user) + var id: UUID? + + @Field(key: "apple_id") + var appleId: Int + + @Parent(key: "channel_id") + var channel: Channel +} + +extension PodcastChannel { + static func upsert(_ newChannel: PodcastChannel, on database: Database) -> EventLoopFuture { + PodcastChannel.find(newChannel.id, on: database) + .optionalMap { $0.appleId == newChannel.appleId ? $0 : nil } + .flatMap { (channel) -> EventLoopFuture in + guard let channelId = newChannel.id, channel == nil else { + return database.eventLoop.makeSucceededFuture(()) + } + + return PodcastChannel.query(on: database).group(.or) { + $0.filter(\.$id == channelId).filter(\.$appleId == newChannel.appleId) + }.all().flatMapEach(on: database.eventLoop) { channel in + channel.delete(on: database) + }.flatMap { _ in + // context.logger.info("saving yt channel \"\(newChannel.youtubeId)\"") + newChannel.save(on: database) + } + } + } +} diff --git a/Sources/OrchardNestServer/Models/DB/PodcastEpisode.swift b/Sources/OrchardNestServer/Models/DB/PodcastEpisode.swift new file mode 100644 index 0000000..7ce4c8b --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/PodcastEpisode.swift @@ -0,0 +1,45 @@ +import Fluent +import Vapor + +final class PodcastEpisode: Model { + static var schema = "podcast_episodes" + + init() {} + + init(entryId: UUID, audioURL: String) { + id = entryId + self.audioURL = audioURL + } + + @ID(custom: "entry_id", generatedBy: .user) + var id: UUID? + + @Field(key: "audio") + var audioURL: String + + @Parent(key: "entry_id") + var entry: Entry +} + +extension PodcastEpisode: Validatable { + static func validations(_ validations: inout Validations) { + validations.add("audioURL", as: URL.self) + } +} + +extension PodcastEpisode { + static func upsert(_ newEpisode: PodcastEpisode, on database: Database) -> EventLoopFuture { + return PodcastEpisode.find(newEpisode.id, on: database) + .flatMap { (episode) -> EventLoopFuture in + let savingEpisode: PodcastEpisode + if let oldEpisode = episode { + oldEpisode.audioURL = newEpisode.audioURL + savingEpisode = oldEpisode + } else { + savingEpisode = newEpisode + } + // context.logger.info("saving podcast episode \"\(savingEpisode.audioURL)\"") + return savingEpisode.save(on: database) + } + } +} diff --git a/Sources/OrchardNestServer/Models/DB/YouTubeChannel.swift b/Sources/OrchardNestServer/Models/DB/YouTubeChannel.swift new file mode 100644 index 0000000..5209bb7 --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/YouTubeChannel.swift @@ -0,0 +1,43 @@ +import Fluent +import Vapor + +final class YouTubeChannel: Model { + static var schema = "youtube_channels" + + init() {} + + init(channelId: UUID, youtubeId: String) { + id = channelId + self.youtubeId = youtubeId + } + + @ID(custom: "channel_id", generatedBy: .user) + var id: UUID? + + @Field(key: "youtube_id") + var youtubeId: String + + @Parent(key: "channel_id") + var channel: Channel +} + +extension YouTubeChannel { + static func upsert(_ newChannel: YouTubeChannel, on database: Database) -> EventLoopFuture { + YouTubeChannel.find(newChannel.id, on: database) + .optionalMap { $0.youtubeId == newChannel.youtubeId ? $0 : nil } + .flatMap { (channel) -> EventLoopFuture in + guard let channelId = newChannel.id, channel == nil else { + return database.eventLoop.makeSucceededFuture(()) + } + + return YouTubeChannel.query(on: database).group(.or) { + $0.filter(\.$id == channelId).filter(\.$youtubeId == newChannel.youtubeId) + }.all().flatMapEach(on: database.eventLoop) { channel in + channel.delete(on: database) + }.flatMap { _ in + // context.logger.info("saving yt channel \"\(newChannel.youtubeId)\"") + newChannel.save(on: database) + } + } + } +} diff --git a/Sources/OrchardNestServer/Models/DB/YouTubeVideo.swift b/Sources/OrchardNestServer/Models/DB/YouTubeVideo.swift new file mode 100644 index 0000000..4219de6 --- /dev/null +++ b/Sources/OrchardNestServer/Models/DB/YouTubeVideo.swift @@ -0,0 +1,43 @@ +import Fluent +import Vapor + +final class YoutubeVideo: Model { + static var schema = "youtube_videos" + + init() {} + + init(entryId: UUID, youtubeId: String) { + id = entryId + self.youtubeId = youtubeId + } + + @ID(custom: "entry_id", generatedBy: .user) + var id: UUID? + + @Field(key: "youtube_id") + var youtubeId: String + + @Parent(key: "entry_id") + var entry: Entry +} + +extension YoutubeVideo { + static func upsert(_ newVideo: YoutubeVideo, on database: Database) -> EventLoopFuture { + return YoutubeVideo.find(newVideo.id, on: database) + .optionalMap { $0.youtubeId == newVideo.youtubeId ? $0 : nil } + .flatMap { (video) -> EventLoopFuture in + guard let entryId = newVideo.id, video == nil else { + return database.eventLoop.makeSucceededFuture(()) + } + + return YoutubeVideo.query(on: database).group(.or) { + $0.filter(\.$id == entryId).filter(\.$youtubeId == newVideo.youtubeId) + }.all().flatMapEach(on: database.eventLoop) { channel in + channel.delete(on: database) + }.flatMap { _ in + // context.logger.info("saving yt video \"\(newVideo.youtubeId)\"") + newVideo.save(on: database) + } + } + } +} diff --git a/Sources/OrchardNestServer/Models/FeedChannel.swift b/Sources/OrchardNestServer/Models/FeedChannel.swift new file mode 100644 index 0000000..2d6ab31 --- /dev/null +++ b/Sources/OrchardNestServer/Models/FeedChannel.swift @@ -0,0 +1,70 @@ +import OrchardNestKit +import Vapor + +#if canImport(FoundationNetworking) + import FoundationNetworking +#endif + +struct EmptyError: Error {} + +extension FeedChannel { + static func parseSite(_ site: OrganizedSite, using client: Client, on eventLoop: EventLoop) -> EventLoopFuture> { + // let uri = URI(string: site.site.feed_url.absoluteString) + // let headers = HTTPHeaders([("Host", uri.host!), ("User-Agent", "OrchardNest-Robot"), ("Accept", "*/*")]) + + // let promise = eventLoop.makePromise(of: Result.self) + return client.get(URI(string: site.site.feed_url.absoluteString)).map { (response) -> Data? in + response.body.map { buffer in + Data(buffer: buffer) + } + }.flatMapAlways { (result) -> EventLoopFuture> in + let newResult = result.mapError { FeedError.download(site.site.feed_url, $0) }.flatMap { (data) -> Result in + guard let data = data else { + return .failure(.empty(site.site.feed_url)) + } + return .success(data) + }.flatMap { data in + Result { + try FeedChannel(language: site.languageCode, category: site.categorySlug, site: site.site, data: data) + }.mapError { .parser(site.site.feed_url, $0) } + }.flatMap { (channel) -> Result in + guard channel.items.count > 0 || channel.itemCount == channel.items.count else { + return .failure(.items(site.site.feed_url)) + } + return .success(channel) + } + return eventLoop.future(newResult) + } +// URLSession.shared.dataTask(with: site.site.feed_url) { data, _, error in +// let result: Result +// if let error = error { +// result = .failure(error) +// } else if let data = data { +// result = .success(data) +// } else { +// promise.fail(EmptyError()) +// return +// } +// promise.succeed(result) +// }.resume() +// return promise.futureResult.flatMap { (result) -> EventLoopFuture> in +// +// let responseBody: Data +// do { +// responseBody = try result.get() +// } catch { +// return eventLoop.future(.failure(.download(site.site.feed_url, error))) +// } +// let channel: FeedChannel +// do { +// channel = try FeedChannel(language: site.languageCode, category: site.categorySlug, site: site.site, data: responseBody) +// } catch { +// return eventLoop.future(.failure(.parser(site.site.feed_url, error))) +// } +// guard channel.items.count > 0 || channel.itemCount == channel.items.count else { +// return eventLoop.future(.failure(.items(site.site.feed_url))) +// } +// return eventLoop.future(.success(channel)) +// } + } +} diff --git a/Sources/OrchardNestServer/Models/FeedConfiguration.swift b/Sources/OrchardNestServer/Models/FeedConfiguration.swift new file mode 100644 index 0000000..8b2252d --- /dev/null +++ b/Sources/OrchardNestServer/Models/FeedConfiguration.swift @@ -0,0 +1,25 @@ +import OrchardNestKit + +struct FeedConfiguration { + let categorySlug: String + let languageCode: String + let channel: FeedChannel +} + +extension FeedConfiguration { + static func from( + categorySlug: String, + languageCode: String, + channel: FeedChannel, + langMap: [String: Language], + catMap: [String: Category] + ) -> FeedResult { + guard let newLangId = langMap[languageCode]?.id else { + return .failure(.invalidParent(channel.feedUrl, languageCode)) + } + guard let newCatId = catMap[categorySlug]?.id else { + return .failure(.invalidParent(channel.feedUrl, categorySlug)) + } + return .success(FeedConfiguration(categorySlug: newCatId, languageCode: newLangId, channel: channel)) + } +} diff --git a/Sources/OrchardNestServer/Models/FeedError.swift b/Sources/OrchardNestServer/Models/FeedError.swift new file mode 100644 index 0000000..e54dfeb --- /dev/null +++ b/Sources/OrchardNestServer/Models/FeedError.swift @@ -0,0 +1,24 @@ +import Foundation + +enum FeedError: Error { + case download(URL, Error) + case empty(URL) + case parser(URL, Error) + case items(URL) + case invalidParent(URL, String) + + var localizedDescription: String { + switch self { + case let .download(url, error): + return "\(url), download, \"\(error)\"" + case let .empty(url): + return "\(url), empty" + case let .invalidParent(url, parent): + return "\(url), parent, \(parent)" + case let .items(url): + return "\(url), items" + case let .parser(url, error): + return "\(url), parser, \"\(error)\"" + } + } +} diff --git a/Sources/OrchardNestServer/Models/FeedItemConfiguration.swift b/Sources/OrchardNestServer/Models/FeedItemConfiguration.swift new file mode 100644 index 0000000..1662018 --- /dev/null +++ b/Sources/OrchardNestServer/Models/FeedItemConfiguration.swift @@ -0,0 +1,7 @@ +import Foundation +import OrchardNestKit + +struct FeedItemConfiguration { + let channelId: UUID + let feedItem: FeedItem +} diff --git a/Sources/OrchardNestServer/Models/FeedItemEntry.swift b/Sources/OrchardNestServer/Models/FeedItemEntry.swift new file mode 100644 index 0000000..4884729 --- /dev/null +++ b/Sources/OrchardNestServer/Models/FeedItemEntry.swift @@ -0,0 +1,46 @@ +import Fluent +import OrchardNestKit +import Vapor + +struct FeedItemEntry { + let entry: Entry + let feedItem: FeedItem + + static func from(upsertOn database: Database, from config: FeedItemConfiguration) -> EventLoopFuture { + Entry.query(on: database).filter(\.$feedId == config.feedItem.id).first().flatMap { foundEntry in + let newEntry: Entry + if let entry = foundEntry { + newEntry = entry + } else { + newEntry = Entry() + } + + newEntry.$channel.id = config.channelId + newEntry.content = config.feedItem.content + newEntry.feedId = config.feedItem.id + newEntry.imageURL = config.feedItem.image?.absoluteString + newEntry.publishedAt = config.feedItem.published + newEntry.summary = config.feedItem.summary + newEntry.title = config.feedItem.title + newEntry.url = config.feedItem.url.absoluteString + // context.logger.info("saving entry for \"\(config.feedItem.url)\"") + return newEntry.save(on: database).transform(to: Self(entry: newEntry, feedItem: config.feedItem)) + } + } +} + +extension FeedItemEntry { + var podcastEpisode: PodcastEpisode? { + guard let id = entry.id, let audioURL = feedItem.audio else { + return nil + } + return PodcastEpisode(entryId: id, audioURL: audioURL.absoluteString) + } + + var youtubeVideo: YoutubeVideo? { + guard let id = entry.id, let youtubeId = feedItem.ytId else { + return nil + } + return YoutubeVideo(entryId: id, youtubeId: youtubeId) + } +} diff --git a/Sources/OrchardNestServer/Models/FeedResult.swift b/Sources/OrchardNestServer/Models/FeedResult.swift new file mode 100644 index 0000000..171c1c6 --- /dev/null +++ b/Sources/OrchardNestServer/Models/FeedResult.swift @@ -0,0 +1 @@ +typealias FeedResult = Result diff --git a/Sources/OrchardNestServer/Models/Model.swift b/Sources/OrchardNestServer/Models/Model.swift new file mode 100644 index 0000000..af3e9d3 --- /dev/null +++ b/Sources/OrchardNestServer/Models/Model.swift @@ -0,0 +1,11 @@ +import Fluent + +extension Model { + static func dictionary(from elements: [Self]) -> [IDType: Self] where IDType == Self.IDValue { + return Dictionary(uniqueKeysWithValues: + elements.compactMap { model in + model.id.map { ($0, model) } + } + ) + } +} diff --git a/Sources/OrchardNestServer/Models/SiteCatalogMap.swift b/Sources/OrchardNestServer/Models/SiteCatalogMap.swift new file mode 100644 index 0000000..b6b49f6 --- /dev/null +++ b/Sources/OrchardNestServer/Models/SiteCatalogMap.swift @@ -0,0 +1,29 @@ +import OrchardNestKit + +struct SiteCatalogMap { + let languages: [String: String] + let categories: [String: [String: String]] + let organizedSites: [OrganizedSite] + + init(sites: [LanguageContent]) { + var languages = [String: String]() + var categories = [String: [String: String]]() + var organizedSites = [OrganizedSite]() + + for lang in sites { + languages[lang.language] = lang.title + for category in lang.categories { + var categoryMap = categories[category.slug] ?? [String: String]() + categoryMap[lang.language] = category.title + categories[category.slug] = categoryMap + organizedSites.append(contentsOf: category.sites.map { + OrganizedSite(languageCode: lang.language, categorySlug: category.slug, site: $0) + }) + } + } + + self.categories = categories + self.languages = languages + self.organizedSites = organizedSites + } +} diff --git a/Sources/OrchardNestServer/OrchardNest.swift b/Sources/OrchardNestServer/OrchardNest.swift deleted file mode 100644 index b468250..0000000 --- a/Sources/OrchardNestServer/OrchardNest.swift +++ /dev/null @@ -1,71 +0,0 @@ -import Fluent -import FluentPostgresDriver -import Vapor - -public protocol ConfiguratorProtocol { - func configure(_ app: Application) throws -} - -// -public final class Configurator: ConfiguratorProtocol { - public static let shared: ConfiguratorProtocol = Configurator() - - // - ///// Called before your application initializes. - public func configure(_ app: Application) throws { - // Register providers first - // try services.register(FluentPostgreSQLProvider()) - // try services.register(AuthenticationProvider()) - - // services.register(DirectoryIndexMiddleware.self) - - // Register middleware - // var middlewares = MiddlewareConfig() // Create _empty_ middleware config - // middlewares.use(SessionsMiddleware.self) // Enables sessions. - let rootPath = Environment.get("ROOT_PATH") ?? app.directory.publicDirectory - -// app.webSockets = WebSocketRepository() -// -// app.middleware.use(DirectoryIndexMiddleware(publicDirectory: rootPath)) - - app.middleware.use(ErrorMiddleware.default(environment: app.environment)) - // middlewares.use(ErrorMiddleware.self) // Catches errors and converts to HTTP response - // services.register(middlewares) - - // Configure a SQLite database - let postgreSQLConfig: PostgresConfiguration - - if let url = Environment.get("DATABASE_URL") { - postgreSQLConfig = PostgresConfiguration(url: url)! - } else { - postgreSQLConfig = PostgresConfiguration(hostname: "localhost", username: "orchardnest") - } - - app.databases.use(.postgres(configuration: postgreSQLConfig), as: .psql) - -// app.databases.middleware.use(UserEmailerMiddleware(app: app)) -// -// app.migrations.add(CreateDevice()) -// app.migrations.add(CreateAppleUser()) -// app.migrations.add(CreateDeviceWorkout()) -// app.migrations.add(ActivateWorkout()) - // let wss = NIOWebSocketServer.default() - -// app.webSocket("api", "v1", "workouts", ":id", "listen") { req, websocket in -// guard let idData = try? Base32CrockfordEncoding.encoding.decode(base32Encoded: req.parameters.get("id")!) else { -// return -// } -// let workoutID = UUID(data: idData) -// -// _ = Workout.find(workoutID, on: req.db).unwrap(or: Abort(HTTPResponseStatus.notFound)).flatMapThrowing { workout in -// let workoutId = try workout.requireID() -// app.webSockets.save(websocket, withID: workoutId) -// } -// } - - // services.register(wss, as: WebSocketServer.self) - app.get { _ in - "Hello" - } - } -} diff --git a/Sources/OrchardNestServer/RefreshCommand.swift b/Sources/OrchardNestServer/RefreshCommand.swift new file mode 100644 index 0000000..9ada1fb --- /dev/null +++ b/Sources/OrchardNestServer/RefreshCommand.swift @@ -0,0 +1,9 @@ +import Vapor + +struct RefreshCommand: Command { + typealias Signature = RefreshConfiguration + + var help: String + + func run(using _: CommandContext, signature _: RefreshConfiguration) throws {} +} diff --git a/Sources/OrchardNestServer/RefreshConfiguration.swift b/Sources/OrchardNestServer/RefreshConfiguration.swift new file mode 100644 index 0000000..91605f3 --- /dev/null +++ b/Sources/OrchardNestServer/RefreshConfiguration.swift @@ -0,0 +1,5 @@ +import Vapor + +struct RefreshConfiguration: CommandSignature, Codable { + init() {} +} diff --git a/Sources/OrchardNestServer/RefreshJob.swift b/Sources/OrchardNestServer/RefreshJob.swift new file mode 100644 index 0000000..fe2ece3 --- /dev/null +++ b/Sources/OrchardNestServer/RefreshJob.swift @@ -0,0 +1,260 @@ +import Fluent +import NIO +import OrchardNestKit +import Queues +import Vapor + +struct ApplePodcastResult: Codable { + let collectionId: Int +} + +struct ApplePodcastResponse: Codable { + let results: [ApplePodcastResult] +} + +struct RefreshJob: ScheduledJob, Job { + func run(context: QueueContext) -> EventLoopFuture { + context.queue.dispatch( + RefreshJob.self, + RefreshConfiguration() + ) + } + + static let url = URL(string: "https://raw.githubusercontent.com/daveverwer/iOSDevDirectory/master/blogs.json")! + + static let basePodcastQueryURLComponents = URLComponents(string: """ + https://itunes.apple.com/search?media=podcast&attribute=titleTerm&limit=1&entity=podcast + """)! + + static func queryURL(forPodcastWithTitle title: String) -> URI { + var components = Self.basePodcastQueryURLComponents + guard var queryItems = components.queryItems else { + preconditionFailure() + } + queryItems.append(URLQueryItem(name: "term", value: title.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed))) + components.queryItems = queryItems + return URI( + scheme: components.scheme, + host: components.host, + port: components.port, + path: components.path, + query: components.query, + fragment: components.fragment + ) + } + + typealias Payload = RefreshConfiguration + + func error(_ context: QueueContext, _ error: Error, _: RefreshConfiguration) -> EventLoopFuture { + context.logger.report(error: error) + return context.eventLoop.future() + } + + // swiftlint:disable:next function_body_length + func dequeue(_ context: QueueContext, _: RefreshConfiguration) -> EventLoopFuture { + let database = context.application.db + let client = context.application.client + + let decoder = JSONDecoder() + + context.logger.info("downloading blog list...") + + return context.application.client.get(URI(string: Self.url.absoluteString)).flatMapThrowing { (response) -> [LanguageContent] in + try response.content.decode([LanguageContent].self, using: decoder) + }.map(SiteCatalogMap.init).flatMap { (siteCatalogMap) -> EventLoopFuture in + + let languages = siteCatalogMap.languages + let categories = siteCatalogMap.categories + let organizedSites = siteCatalogMap.organizedSites + + let futureLanguages = languages.map { Language.from($0, on: database) }.flatten(on: database.eventLoop) + let futureCategories = categories.map { Category.from($0.key, on: database) }.flatten(on: database.eventLoop) + + let langMap = futureLanguages.map(Language.dictionary(from:)) + let catMap = futureCategories.map(Category.dictionary(from:)) + + // need map to lang, cats + + let futureFeedResults: EventLoopFuture<[FeedResult]> + futureFeedResults = langMap.and(catMap).flatMap { (maps) -> EventLoopFuture<[FeedResult]> in + context.logger.info("downloading feeds...") + + let (langMap, catMap) = maps + var results = [EventLoopFuture]() + let promise = context.eventLoop.makePromise(of: Void.self) + _ = context.eventLoop.scheduleRepeatedAsyncTask( + initialDelay: .seconds(1), + delay: .nanoseconds(20_000_000) + ) { (task: RepeatedTask) -> EventLoopFuture in + guard results.count < organizedSites.count else { + task.cancel(promise: promise) + + context.logger.info("finished downloading feeds...") + return context.eventLoop.makeSucceededFuture(()) + } + let args = organizedSites[results.count] + context.logger.info("downloading \"\(args.site.feed_url)\"") + let result = FeedChannel.parseSite(args, using: client, on: context.eventLoop).map { result in + result.flatMap { FeedConfiguration.from( + categorySlug: args.categorySlug, + languageCode: args.languageCode, + channel: $0, + langMap: langMap, + catMap: catMap + ) + } + } + results.append(result) + return result.transform(to: ()) + } + let finalResults = promise.futureResult.flatMap { + results.flatten(on: context.eventLoop) + } + + return finalResults +// return organizedSites.map { orgSite in +// FeedChannel.parseSite(orgSite, using: context.application.client, on: context.eventLoop) +// .map { result in +// result.flatMap { FeedConfiguration.from( +// categorySlug: orgSite.categorySlug, +// languageCode: orgSite.languageCode, +// channel: $0, +// langMap: langMap, +// catMap: catMap +// ) +// } +// } +// }.flatten(on: context.eventLoop) + } + + let groupedResults = futureFeedResults.map { results -> ([FeedConfiguration], [FeedError]) in + var errors = [FeedError]() + var configurations = [FeedConfiguration]() + results.forEach { + switch $0 { + case let .success(config): configurations.append(config) + case let .failure(error): errors.append(error) + } + } + return (configurations, errors) + } + + groupedResults.whenSuccess { groupedResults in + let errors = groupedResults.1 + for error in errors { + context.logger.info("\(error.localizedDescription)") + } + } + + return database.transaction { database in + let futureFeeds = groupedResults.map { $0.0 }.map { configs -> [FeedConfiguration] in + let feeds = Dictionary(grouping: configs) { $0.channel.feedUrl } + return feeds.compactMap { $0.value.first } + } + let currentChannels = futureFeeds.map { (args) -> [String] in + args.map { $0.channel.feedUrl.absoluteString } + }.flatMap { feedUrls in + Channel.query(on: database).filter(\.$feedUrl ~~ feedUrls).with(\.$podcasts).all() + }.map { + Dictionary(uniqueKeysWithValues: ($0.map { + ($0.feedUrl, $0) + })) + } + + let futureChannels = futureFeeds.and(currentChannels).map { (args) -> [ChannelFeedItemsConfiguration] in + context.logger.info("beginning upserting channels...") + let (feeds, currentChannels) = args + + return feeds.map { feedArgs in + ChannelFeedItemsConfiguration(channels: currentChannels, feedArgs: feedArgs) + } + }.flatMap { (configurations) -> EventLoopFuture<[ChannelFeedItemsConfiguration]> in + + database.withConnection { (database) -> EventLoopFuture<[ChannelFeedItemsConfiguration]> in + + var results = [EventLoopFuture]() + let promise = context.eventLoop.makePromise(of: Void.self) + _ = context.eventLoop.scheduleRepeatedAsyncTask( + initialDelay: .seconds(1), + delay: .nanoseconds(20_000_000) + ) { (task: RepeatedTask) -> EventLoopFuture in + guard results.count < configurations.count else { + task.cancel(promise: promise) + + context.logger.info("finished upserting channels...") + return context.eventLoop.makeSucceededFuture(()) + } + let args = configurations[results.count] + context.logger.info("saving \"\(args.channel.title)\"") + let result = args.channel.save(on: database).transform(to: args).flatMapError { _ -> EventLoopFuture in + database.eventLoop.future(ChannelFeedItemsConfiguration?.none) + } + results.append(result) + return result.transform(to: ()) + } + let finalResults = promise.futureResult.flatMap { + results.flatten(on: context.eventLoop).mapEachCompact { $0 } + } + + return finalResults + } + } + + let podcastChannels = futureChannels.mapEachCompact { (configuration) -> Channel? in + let hasPodcastEpisode = (configuration.items.first { $0.audio != nil }) != nil + + guard hasPodcastEpisode || configuration.channel.$category.id == "podcasts" else { + return nil + } + if let podcasts = configuration.channel.$podcasts.value { + guard podcasts.count == 0 else { + return nil + } + } + return configuration.channel + }.flatMapEachCompact(on: context.eventLoop) { (channel) -> EventLoopFuture in + client.get(Self.queryURL(forPodcastWithTitle: channel.title)).flatMapThrowing { + try $0.content.decode(ApplePodcastResponse.self, using: decoder) + }.map { (response) -> (PodcastChannel?) in + response.results.first.flatMap { result in + channel.id.map { ($0, result.collectionId) } + }.map(PodcastChannel.init) + }.recover { _ in nil } + }.flatMapEach(on: context.eventLoop) { $0.create(on: database) } + + // save youtube channels to channels + let futYTChannels = futureChannels.mapEachCompact { (channel) -> YouTubeChannel? in + channel.youtubeChannel + }.flatMapEach(on: database.eventLoop) { newChannel in + YouTubeChannel.upsert(newChannel, on: database) + } + + // save entries to channels + let futureEntries = futureChannels + .flatMapEachThrowing { try $0.feedItems() } + .map { $0.flatMap { $0 } } + .flatMapEach(on: database.eventLoop) { (config) -> EventLoopFuture in + FeedItemEntry.from(upsertOn: database, from: config) + } + + // save videos to entries + let futYTVideos = futureEntries.mapEachCompact { (entry) -> YoutubeVideo? in + entry.youtubeVideo + }.flatMapEach(on: database.eventLoop) { newVideo in + YoutubeVideo.upsert(newVideo, on: database) + } + + // save podcastepisodes to entries + + let futPodEpisodes = futureEntries.mapEachCompact { (entry) -> PodcastEpisode? in + + entry.podcastEpisode + }.flatMapEach(on: database.eventLoop) { newEpisode in + PodcastEpisode.upsert(newEpisode, on: database) + } + + return futYTVideos.and(futYTChannels).and(futPodEpisodes).and(podcastChannels).transform(to: ()) + } + } + } +} diff --git a/Sources/orcnst-serve/main.swift b/Sources/orchardnestd/main.swift similarity index 100% rename from Sources/orcnst-serve/main.swift rename to Sources/orchardnestd/main.swift diff --git a/Sources/orcnst/main.swift b/Sources/orcnst/main.swift deleted file mode 100644 index 642ea0c..0000000 --- a/Sources/orcnst/main.swift +++ /dev/null @@ -1,51 +0,0 @@ -import Foundation -import OrchardNestKit - -typealias OrganizedSite = (String, String, Site) - -if true { - let blogs = URL(string: "https://raw.githubusercontent.com/daveverwer/iOSDevDirectory/master/blogs.json")! - - let reader = BlogReader() - let sites = try reader.sites(fromURL: blogs) - - let orgSites = sites.flatMap { (content) -> [OrganizedSite] in - let language = content.language - return content.categories.flatMap { (category) -> [OrganizedSite] in - category.sites.map { - (language, category.slug, $0) - } - } - } - - let channelResults = orgSites.map { args in - Result { - try Channel(language: args.0, category: args.1, site: args.2) - } - } - - var errors = [Error]() - var channels = [Channel]() - - for result in channelResults { - switch result { - case let .failure(error): - errors.append(error) - case let .success(channel): - channels.append(channel) - } - } - - debugPrint(errors) - - let encoder = JSONEncoder() - encoder.outputFormatting = .prettyPrinted - let data = try encoder.encode(channels) - - try data.write(to: - URL(fileURLWithPath: "/Users/leo/data.json") - ) - -} else { - let data = try Data(contentsOf: URL(fileURLWithPath: "/Users/leo/Downloads/data.json")) -} diff --git a/Tests/LinuxMain.swift b/Tests/LinuxMain.swift index 7371d47..01dfd5c 100644 --- a/Tests/LinuxMain.swift +++ b/Tests/LinuxMain.swift @@ -1,7 +1,8 @@ import XCTest -import OrchardNestTests +import OrchardNestKitTests var tests = [XCTestCaseEntry]() -tests += OrchardNestTests.allTests() +tests += OrchardNestKitTests.__allTests() + XCTMain(tests) diff --git a/Tests/OrchardNestKitTests/XCTestManifests.swift b/Tests/OrchardNestKitTests/XCTestManifests.swift index 16d86a1..d864e79 100644 --- a/Tests/OrchardNestKitTests/XCTestManifests.swift +++ b/Tests/OrchardNestKitTests/XCTestManifests.swift @@ -1,9 +1,18 @@ -import XCTest - #if !canImport(ObjectiveC) - public func allTests() -> [XCTestCaseEntry] { + import XCTest + + extension OrchardNestTests { + // DO NOT MODIFY: This is autogenerated, use: + // `swift test --generate-linuxmain` + // to regenerate. + static let __allTests__OrchardNestTests = [ + ("testExample", testExample) + ] + } + + public func __allTests() -> [XCTestCaseEntry] { return [ - testCase(OrchardNestTests.allTests) + testCase(OrchardNestTests.__allTests__OrchardNestTests) ] } #endif diff --git a/create_db.sql b/create_db.sql new file mode 100644 index 0000000..43309fe --- /dev/null +++ b/create_db.sql @@ -0,0 +1,3 @@ +create database orchardnest; +create user orchardnest; +grant all privileges on database orchardnest to orchardnest; \ No newline at end of file diff --git a/server.sh b/server.sh new file mode 100644 index 0000000..15244ed --- /dev/null +++ b/server.sh @@ -0,0 +1,37 @@ +apt update +apt -y full-upgrade +apt -y install tmux supervisor postgresql nginx zsh \ + binutils \ + git \ + gnupg2 \ + libc6-dev \ + libcurl4 \ + libedit2 \ + libgcc-9-dev \ + libpython2.7 \ + libsqlite3-0 \ + libstdc++-9-dev \ + libxml2 \ + libz3-dev \ + pkg-config \ + tzdata \ + zlib1g-dev + +sh -c "$(curl -fsSL https://raw.githubusercontent.com/ohmyzsh/ohmyzsh/master/tools/install.sh)" "" --unattended +curl https://swift.org/builds/swift-5.2.5-release/ubuntu2004/swift-5.2.5-RELEASE/swift-5.2.5-RELEASE-ubuntu20.04.tar.gz | tar xzf - -C /usr/share/ +mv /usr/share/swift-5.2.5-RELEASE-ubuntu20.04 /usr/share/swift +export PATH=/usr/share/swift/usr/bin:"${PATH}" + +# download swift + +# create db and user with password + + +# as orchardnest user +git clone https://github.com/brightdigit/OrchardNest.git app +swift build -c release --enable-test-discovery + +sudo -u postgres createuser orchardnest +sudo -u postgres createdb orchardnest +sudo -u postgres psql -c "alter user orchardnest with encrypted password '12345';" +sudo -u postgres psql -c "grant all privileges on database orchardnest to orchardnest;"