...
 
Commits (15)
......@@ -4,41 +4,32 @@ This guide covers our recommended options for FLO (Free/Libre/Open) text-editors
and IDE development tools for hacking on Snowdrift (NB: most of this applies
equally to any Yesod-based project).
**NB:** notice the section about jumping to definitions. It's down a ways.
## Text editor packages and settings
### Atom
[Atom](https://atom.io/) is a modern, graphical, highly-extensible text-editor,
good for beginners and advanced alike.
good for beginners and advanced alike.
We recommend installing the `atom-haskell` package and then following the
*Binaries* section of [atom-haskell's README](https://github.com/Tehnix/atom-haskell),
choosing `brittany` as the beautifier. Please note the blurb about
`atom-hasklig` above *Quick configuration*. NB: these "packages" are Atom
plugins, not distro packages.
For more thorough doc, see [the official Atom-Haskell documentation](https://atom-haskell.github.io/).
NB: provided we have `stack.yaml` right, you should be able to ignore any blurb
boxes about specific versions of GHC / lts.
#### Atom settings
Some general settings:
* In the main Atom settings, leave soft tabs checked and set 4-space tabs
* Packages/Tree View: consider "Hide Ignored Names" and "Hide VCS Ignored Files"
#### Atom packages
We recommend at least the Atom packages:
* [language-shakespeare](https://atom.io/packages/language-shakespeare)
* in the language-shakespeare package settings, change the hamlet tab-length
to 2, leave others at 4
* [language-haskell](https://atom.io/packages/language-haskell)
The [ide-haskell](https://atom.io/packages/ide-haskell) package offers further
development tools including error-checking, linting, and type information. To
install ide-haskell for Atom:
* *Note: after many changes to snowdrift build process and file structure,
ghc-mod may or may not work, testing is needed.*
* Run `stack build ghc-mod hlint stylish-haskell`
* Install the relevant Atom packages:
`apm install language-haskell haskell-ghc-mod ide-haskell autocomplete-haskell ide-haskell-cabal`
* Make sure to check "Stack sandbox" in the haskell-ghc-mod settings
* In the main Atom settings, leave soft tabs checked and set 4-space tabs.
* Packages/Tree View: consider "Hide Ignored Names" and "Hide VCS Ignored Files".
Other useful Atom packages to consider:
#### Atom packages to consider
* Various Git-related tools:
* [tree-view-git-status](https://atom.io/packages/tree-view-git-status)
......
......@@ -25,4 +25,7 @@ main = runScript $ do
-- NB! The string passed to runPersistKeter must match the APPNAME used in
-- keter.sh to deploy the app. Must fix. (Duplicate comment from
-- CrowdmatchMain.)
scriptIO (runPersistKeter "SnowdriftReboot" (makePayments (runStripe conf)))
scriptIO $
runPersistKeter "SnowdriftReboot" $
makePayments $
stripeProduction conf
......@@ -23,6 +23,7 @@ library
Crowdmatch.Model
Crowdmatch.ModelDataTypes
Crowdmatch.Skeleton
Crowdmatch.Stripe
-- other-modules:
-- other-extensions:
hs-source-dirs: src
......
......@@ -14,8 +14,10 @@ module Crowdmatch (
, SqlRunner
-- * Interface with stripe
, StripeRunner
, runStripe
, StripeActions (..)
, stripeProduction
, StripeDevState (..)
, stripeDevelopment
-- * Store/delete payment tokens
, storePaymentToken
......@@ -50,7 +52,6 @@ module Crowdmatch (
-- * Internal stuff, mostly for tests
, CrowdmatchI(..)
, runMech
, StripeI(..)
, PPtr(..)
, centsToUnits
, unitsToCents
......@@ -58,7 +59,7 @@ module Crowdmatch (
) where
import Control.Error (ExceptT(..), runExceptT, note)
import Control.Lens ((^.), from, view, Iso', iso)
import Control.Lens ((^.), from)
import Control.Monad (void)
import Control.Monad.IO.Class (MonadIO, liftIO)
import Data.Function (on)
......@@ -68,15 +69,13 @@ import Data.Time (UTCTime, getCurrentTime, utctDay)
import Database.Persist
import Database.Persist.Sql (SqlPersistT)
import System.IO
import Web.Stripe (stripe, (-&-), StripeConfig, Expandable(..), StripeError)
import Web.Stripe.Balance
import Web.Stripe.Charge
import Web.Stripe.Customer
( updateCustomer
, createCustomer
, deleteCustomer)
import Web.Stripe (Expandable (..), StripeError)
import Web.Stripe.Balance (BalanceTransaction (..))
import Web.Stripe.Charge (Charge (..))
import Web.Stripe.Customer (TokenId, CustomerId, Customer (..))
import Crowdmatch.Model hiding (Patron(..))
import Crowdmatch.Stripe
import qualified Crowdmatch.Model as Model
import qualified Crowdmatch.Skeleton as Skeleton
......@@ -89,11 +88,6 @@ import qualified Crowdmatch.Skeleton as Skeleton
-- | A method that runs 'SqlPersistT' values in your environment.
type SqlRunner io env = forall a. SqlPersistT io a -> env a
-- | A method that runs 'StripeI' instructions in IO. A default that uses
-- 'stripe' is provided by 'runStripe'.
type StripeRunner = forall io.
MonadIO io => forall a. StripeI a -> io (Either StripeError a)
--
-- THE ACTUAL INTERFACE USED BY THE WEBSITE
--
......@@ -121,7 +115,7 @@ data Project = Project
-- | Record a 'TokenId' for a patron.
storePaymentToken
:: (ToCrowdmatchPatron usr, MonadIO env)
=> StripeRunner
=> StripeActions
-> usr -- ^ your model's user, an instance of ToCrowdmatchPatron
-> TokenId -- ^ you must independently get this from stripe
-> SqlPersistT env (Either StripeError ())
......@@ -135,7 +129,7 @@ storePaymentToken strp usr =
-- a token is required for pledging.
deletePaymentToken
:: (ToCrowdmatchPatron usr, MonadIO env)
=> StripeRunner -- ^
=> StripeActions -- ^
-> usr
-> SqlPersistT env (Either StripeError ())
deletePaymentToken strp =
......@@ -184,7 +178,7 @@ crowdmatch = runMech CrowdmatchI
-- operational mistakes, rather than bad/duplicate charges. :)
makePayments
:: MonadIO env
=> StripeRunner -- ^
=> StripeActions -- ^
-> SqlPersistT env ()
makePayments strp = runMech (MakePaymentsI strp)
......@@ -196,12 +190,12 @@ makePayments strp = runMech (MakePaymentsI strp)
-- | Actions provided by the library
data CrowdmatchI return where
StorePaymentTokenI
:: StripeRunner
:: StripeActions
-> PPtr
-> TokenId
-> CrowdmatchI (Either StripeError ())
DeletePaymentTokenI
:: StripeRunner
:: StripeActions
-> PPtr
-> CrowdmatchI (Either StripeError ())
StorePledgeI :: PPtr -> CrowdmatchI ()
......@@ -209,7 +203,7 @@ data CrowdmatchI return where
FetchProjectI :: CrowdmatchI Project
FetchPatronI :: PPtr -> CrowdmatchI Patron
CrowdmatchI :: CrowdmatchI ()
MakePaymentsI :: StripeRunner -> CrowdmatchI ()
MakePaymentsI :: StripeActions -> CrowdmatchI ()
-- | Executing the actions
runMech :: MonadIO env => CrowdmatchI return -> SqlPersistT env return
......@@ -224,8 +218,8 @@ runMech (StorePaymentTokenI strp pptr cardToken) = do
ret <- ExceptT $ maybe create' update' (Model.patronPaymentToken p)
ExceptT (Right <$> updatePatron' pid ret)
where
create' = stripeCreateCustomer strp cardToken
update' = stripeUpdateCustomer strp cardToken . unPaymentToken
create' = liftIO $ createCustomer strp cardToken
update' = liftIO . updateCustomer strp cardToken . unPaymentToken
updatePatron' pid c = do
now <- liftIO getCurrentTime
let payToken = PaymentToken (customerId c)
......@@ -238,7 +232,7 @@ runMech (DeletePaymentTokenI strp pptr) = do
maybe (pure (Right ())) (deleteToken' pid) (Model.patronPaymentToken p)
where
deleteToken' pid (PaymentToken cust) = do
res <- stripeDeleteCustomer strp cust
res <- liftIO $ deleteCustomer strp cust
traverse (const (onStripeSuccess' pid)) res
onStripeSuccess' pid = do
now <- liftIO getCurrentTime
......@@ -353,7 +347,7 @@ runMech (MakePaymentsI strp) = do
sendCharge Donor{..} =
fmap
(fmap (ChargeResult _donorPatron fee net))
(stripeChargeCustomer strp _donorCustomer cents)
(liftIO $ chargeCustomer strp _donorCustomer cents)
where
cents = payment (unitsToCents _donorDonationPayable)
fee = stripeFee cents
......@@ -373,7 +367,7 @@ runMech (MakePaymentsI strp) = do
PayOk chargeResult -> recordDonation chargeResult
where
recordDonation ChargeResult{..} = do
ts <- liftIO (stripeDonationTimestamp strp _chargeResultCharge)
ts <- stripeDonationTimestamp strp _chargeResultCharge
insert_
(DonationHistory
_chargeResultPatron
......@@ -391,33 +385,6 @@ runMech (MakePaymentsI strp) = do
-- Wherein we abstract over the possible ways of running Stripe.
--
stripeCreateCustomer
:: MonadIO io => StripeRunner -> TokenId -> io (Either StripeError Customer)
stripeCreateCustomer strp = liftIO . strp . CreateCustomerI
stripeUpdateCustomer
:: MonadIO io
=> StripeRunner
-> TokenId
-> CustomerId
-> io (Either StripeError Customer)
stripeUpdateCustomer strp tok = liftIO . strp . UpdateCustomerI tok
stripeDeleteCustomer
:: MonadIO io
=> StripeRunner
-> CustomerId
-> io (Either StripeError ())
stripeDeleteCustomer strp = liftIO . strp . DeleteCustomerI
stripeChargeCustomer
:: MonadIO io
=> StripeRunner
-> CustomerId
-> Cents
-> io (Either StripeError Charge)
stripeChargeCustomer strp cust cents = liftIO . strp $ ChargeCustomerI cust cents
-- | Tries to get the timestamp from the Charge's TransactionBalance
-- sub-item. If that fails, it's cool, we'll just use a local variant of
-- "now".
......@@ -429,7 +396,7 @@ stripeChargeCustomer strp cust cents = liftIO . strp $ ChargeCustomerI cust cent
-- status, but let's slap that together later.
stripeDonationTimestamp
:: MonadIO io
=> StripeRunner -> Charge -> io HistoryTime
=> StripeActions -> Charge -> io HistoryTime
stripeDonationTimestamp strp charge = fmap HistoryTime (chargeTime charge)
where
fallback = liftIO getCurrentTime
......@@ -439,50 +406,12 @@ stripeDonationTimestamp strp charge = fmap HistoryTime (chargeTime charge)
Expanded BalanceTransaction{..} -> pure balanceTransactionCreated
Id transId -> (=<<)
(either (const fallback) (pure . balanceTransactionCreated))
(strp (BalanceTransactionI transId))
-- | Stripe instructions we use
data StripeI a where
CreateCustomerI :: TokenId -> StripeI Customer
UpdateCustomerI :: TokenId -> CustomerId -> StripeI Customer
DeleteCustomerI :: CustomerId -> StripeI ()
ChargeCustomerI :: CustomerId -> Cents -> StripeI Charge
BalanceTransactionI :: TransactionId -> StripeI BalanceTransaction
-- | A default stripe runner
runStripe
:: MonadIO io
=> StripeConfig -> StripeI a -> io (Either StripeError a)
runStripe c = \case
CreateCustomerI cardToken ->
liftIO (stripe c (createCustomer -&- cardToken))
UpdateCustomerI cardToken cust ->
liftIO (stripe c (updateCustomer cust -&- cardToken))
DeleteCustomerI cust ->
void <$> liftIO (stripe c (deleteCustomer cust))
ChargeCustomerI cust cents ->
liftIO
. stripe c
. (-&- cust)
-- Supported upstream as of 2016-10-06, but not in our resolver yet
-- . (-&- ExpandParams ["balance_transaction"])
. flip createCharge USD
. view chargeCents
$ cents
BalanceTransactionI transId ->
liftIO (stripe c (getBalanceTransaction transId))
(liftIO $ balanceTransaction strp transId)
--
-- Making payments
--
-- | Stripe measures charges in cents. Handy!
chargeCents :: Iso' Cents Amount
chargeCents = iso toAmount fromAmount
where
toAmount (Cents i) = Amount (fromIntegral i)
fromAmount (Amount i) = Cents (fromIntegral i)
data ChargeResult = ChargeResult
{ _chargeResultPatron :: PatronId
, _chargeResultFee :: Cents
......
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE RecordWildCards #-}
-- | This is a wrapper for all Stripe actions used by Snowdrift. It's not
-- specific just to crowdmatch and may involve actions the web app uses and
-- crowdmatch does not, so technically we could make it a separate library. But
-- it feels like too much for a tiny single internal module, so for now it's
-- here.
module Crowdmatch.Stripe
( StripeActions (..)
, chargeCents
, stripeProduction
, StripeDevState (..)
, stripeDevelopment
)
where
import Control.Arrow (right)
import Control.Concurrent (MVar, modifyMVar_)
import Control.Lens (Iso', iso, view)
import Web.Stripe (stripe, (-&-), StripeConfig, Expandable(..), StripeError)
import Web.Stripe.Balance
import Web.Stripe.Charge
import Web.Stripe.Customer hiding
( updateCustomer
, createCustomer
, deleteCustomer
)
import qualified Web.Stripe.Customer as SC
import Crowdmatch.ModelDataTypes
data StripeActions = StripeActions
{ createCustomer
:: TokenId -> IO (Either StripeError Customer)
, updateCustomer
:: TokenId -> CustomerId -> IO (Either StripeError Customer)
, deleteCustomer
:: CustomerId -> IO (Either StripeError ())
, chargeCustomer
:: CustomerId -> Cents -> IO (Either StripeError Charge)
, balanceTransaction
:: TransactionId -> IO (Either StripeError BalanceTransaction)
}
-- | Stripe measures charges in cents. Handy!
chargeCents :: Iso' Cents Amount
chargeCents = iso toAmount fromAmount
where
toAmount (Cents i) = Amount (fromIntegral i)
fromAmount (Amount i) = Cents (fromIntegral i)
stripeProduction :: StripeConfig -> StripeActions
stripeProduction c = StripeActions
{ createCustomer =
\ cardToken -> stripe c $ SC.createCustomer -&- cardToken
, updateCustomer =
\ cardToken cust -> stripe c $ SC.updateCustomer cust -&- cardToken
, deleteCustomer =
\ cust -> right (const ()) <$> stripe c (SC.deleteCustomer cust)
, chargeCustomer =
\ cust cents ->
stripe c
. (-&- cust)
-- Supported upstream as of 2016-10-06, but not in our resolver yet
-- . (-&- ExpandParams ["balance_transaction"])
. flip createCharge USD
. view chargeCents
$ cents
, balanceTransaction =
\ transId -> stripe c $ getBalanceTransaction transId
}
newtype StripeDevState = StripeDevState { lastCharge :: Maybe Cents }
deriving (Eq, Show)
-- | Use this instead of actually talking to Stripe during tests. Uses an MVar
-- to maintain stripe's internal state.
stripeDevelopment :: MVar StripeDevState -> StripeActions
stripeDevelopment state = StripeActions
{ createCustomer =
\ _tok -> pure (Right Customer { customerId = CustomerId "dummy" })
, updateCustomer =
\ _tok cust -> pure (Right Customer { customerId = cust })
, deleteCustomer =
\ _ -> pure (Right ())
, chargeCustomer =
\ _ c -> do
modifyMVar_ state
(\StripeDevState {..} ->
pure StripeDevState { lastCharge = Just c, .. })
pure (Right Charge{ chargeBalanceTransaction = Nothing })
, balanceTransaction =
\ _ -> pure (Right BalanceTransaction{})
}
This diff is collapsed.
#!/usr/bin/env stack
-- stack --install-ghc runghc --package turtle --package shake -- -rtsopts -with-rtsopts=-I0 -O0
{-
*Heads up:* this file will be replaced with a Makefile.
This will also affect build.sh.
-}
-- stack --install-ghc runghc --package turtle --package shake
{-# LANGUAGE OverloadedStrings #-}
-- | sdb.hs - a standalone script to manage a local development postgres cluster.
-- Code notes:
......
......@@ -9,9 +9,8 @@ maintainer: dev@lists.snowdrift.coop
synopsis: Infrastructure for the Snowdrift.coop fundraising site.
category: Web
stability: Experimental
build-type: Simple
homepage: https://snowdrift.coop
bug-reports: https://tree.taiga.io/project/snowdrift/issues
bug-reports: https://git.snowdrift.coop/sd/snowdrift/issues
source-repository head
type: git
......
......@@ -10,29 +10,25 @@ module AppDataTypes where
import ClassyPrelude.Yesod
import Database.Persist.Sql (ConnectionPool)
import Web.Stripe
import Yesod.Core.Types (Logger)
import Yesod.GitRev
import Crowdmatch (StripeActions)
import AuthSite
import Settings
-- | The God-object available to every Handler. This is the site's
-- foundation ("yesod").
data App = App
{ appSettings :: AppSettings
, appStatic :: Static -- ^ Settings for static file serving.
, appConnPool :: ConnectionPool -- ^ Database connection pool.
, appHttpManager :: Manager
, appLogger :: Logger
, appGitRev :: GitRev
, appAuth :: AuthSite
-- | The function for doing stripe API calls. Swapped out for a mock
-- thing in tests.
, appStripe :: forall a. (Typeable (StripeReturn a), FromJSON (StripeReturn a))
=> StripeConfig
-> StripeRequest a
-> IO (Either StripeError (StripeReturn a))
{ appSettings :: AppSettings
, appStatic :: Static -- ^ Settings for static file serving.
, appConnPool :: ConnectionPool -- ^ Database connection pool.
, appHttpManager :: Manager
, appLogger :: Logger
, appGitRev :: GitRev
, appAuth :: AuthSite
, appStripeActions :: StripeActions
}
-- This function generates the route types, and also generates the
......
......@@ -16,7 +16,8 @@ module Application
import Import
import Control.Monad.Logger (liftLoc, runLoggingT)
import Crowdmatch (crowdmatchManualMigrations, migrateCrowdmatch)
import Crowdmatch
(crowdmatchManualMigrations, migrateCrowdmatch, stripeProduction)
import Database.Persist.Postgresql
(createPostgresqlPool, pgConnStr, pgPoolSize, runSqlPool)
import Database.Persist.Sql (runMigrationSilent)
......@@ -60,11 +61,11 @@ makeFoundation appSettings = do
(if appMutableStatic appSettings then staticDevel else static)
(appStaticDir appSettings)
let appStripe :: (Typeable (StripeReturn a), FromJSON (StripeReturn a))
=> StripeConfig
-> StripeRequest a
-> IO (Either StripeError (StripeReturn a))
appStripe = stripe
let conf = StripeConfig $ appStripeSecretKey appSettings
appStripeActions =
if runningDevelopment
then stripeProduction conf
else stripeProduction conf
let appAuth = AuthSite
-- We need a log function to create a connection pool. We need a connection
......
......@@ -214,3 +214,17 @@ navbarLayout pageName widget = do
classes = T.unwords
. List.tail
. T.splitOn "/"
defaultLayoutNew :: Widget -> Handler Html
defaultLayoutNew widget = do
maybeUser <- maybeAuth
let navbar :: Widget
navbar = $(widgetFile "main/navbar")
-- This should catch only main.hamlet, because _main.sass is already
-- @import-ed in page SASS and we don't need to load it here
pc <- widgetToPageContent $(widgetFile "main/main")
withUrlRenderer $(hamletFile "templates/main/main-wrapper.hamlet")
where
footer :: Widget
footer = $(widgetFile "main/footer")
......@@ -33,7 +33,11 @@ getTermsR :: Handler Html
getTermsR = $(widget "page/terms" "Terms of Use")
getProjectsR :: Handler Html
getProjectsR = $(widget "page/projects" "Projects")
getProjectsR = do
loggedIn <- isJust <$> maybeAuth
defaultLayoutNew $ do
setTitle "Projects"
$(widgetFile "page/projects")
getTrademarksR :: Handler Html
getTrademarksR = $(widget "page/trademarks" "Trademarks")
......
......@@ -10,7 +10,7 @@ import Import
import Control.Monad.Logger
import Crowdmatch
import Text.Julius (rawJS)
import Web.Stripe
import Web.Stripe hiding (stripe)
import Web.Stripe.Customer
import Alerts
......@@ -53,20 +53,15 @@ deletePaymentInfoForm :: Form ()
deletePaymentInfoForm =
identifyForm delFormId (renderDivsNoLabels deleteFromPost)
stripeConf :: Handler StripeConfig
stripeConf = fmap
(StripeConfig . appStripeSecretKey . appSettings)
getYesod
postPaymentInfoR :: Handler Html
postPaymentInfoR = handleDelete delFormId deletePaymentInfoR $ do
Entity uid User{..} <- requireAuth
conf <- stripeConf
stripe <- stripeActions
((formResult, _), _) <-
runFormPost (identifyForm modFormId (paymentForm ""))
case formResult of
FormSuccess token -> do
stripeRes <- runDB $ storePaymentToken (runStripe conf) uid token
stripeRes <- runDB $ storePaymentToken stripe uid token
case stripeRes of
Left e -> stripeError e
Right _ -> do
......@@ -78,9 +73,9 @@ postPaymentInfoR = handleDelete delFormId deletePaymentInfoR $ do
deletePaymentInfoR :: Handler Html
deletePaymentInfoR = do
conf <- stripeConf
stripe <- stripeActions
Entity uid User {..} <- requireAuth
stripeDeletionHandler =<< runDB (deletePaymentToken (runStripe conf) uid)
stripeDeletionHandler =<< runDB (deletePaymentToken stripe uid)
redirect DashboardR
where
stripeDeletionHandler =
......
module Handler.Util
( snowdriftTitle
, snowdriftDashTitle
, snowstripe
, stripeActions
-- * DELETE handling
, deleteFromPost
, handleDelete
......@@ -11,7 +11,6 @@ import Import.NoFoundation
import Crowdmatch
import Data.Text.Titlecase
import Web.Stripe
import AppDataTypes
......@@ -22,12 +21,8 @@ snowdriftTitle t = setTitle $
snowdriftDashTitle :: MonadWidget m => Text -> Text -> m ()
snowdriftDashTitle x y = snowdriftTitle $ x `mappend` " — " `mappend` y
snowstripe :: StripeI a -> Handler (Either StripeError a)
snowstripe req = do
conf <- fmap
(StripeConfig . appStripeSecretKey . appSettings)
getYesod
liftIO (runStripe conf req)
stripeActions :: Handler StripeActions
stripeActions = getsYesod appStripeActions
-- | The form element that can be inserted to handle a delete.
deleteFromPost
......
Copyright (c) 2011, Vernon Adams (vern@newtypography.co.uk), with Reserved Font Name Nunito
This Font Software is licensed under the SIL Open Font License, Version 1.1.
This license is copied below, and is also available with a FAQ at:
http://scripts.sil.org/OFL
-----------------------------------------------------------
SIL OPEN FONT LICENSE Version 1.1 - 26 February 2007
-----------------------------------------------------------
PREAMBLE
The goals of the Open Font License (OFL) are to stimulate worldwide
development of collaborative font projects, to support the font creation
efforts of academic and linguistic communities, and to provide a free and
open framework in which fonts may be shared and improved in partnership
with others.
The OFL allows the licensed fonts to be used, studied, modified and
redistributed freely as long as they are not sold by themselves. The
fonts, including any derivative works, can be bundled, embedded,
redistributed and/or sold with any software provided that any reserved
names are not used by derivative works. The fonts and derivatives,
however, cannot be released under any other type of license. The
requirement for fonts to remain under this license does not apply
to any document created using the fonts or their derivatives.
DEFINITIONS
"Font Software" refers to the set of files released by the Copyright
Holder(s) under this license and clearly marked as such. This may
include source files, build scripts and documentation.
"Reserved Font Name" refers to any names specified as such after the
copyright statement(s).
"Original Version" refers to the collection of Font Software components as
distributed by the Copyright Holder(s).
"Modified Version" refers to any derivative made by adding to, deleting,
or substituting -- in part or in whole -- any of the components of the
Original Version, by changing formats or by porting the Font Software to a
new environment.
"Author" refers to any designer, engineer, programmer, technical
writer or other person who contributed to the Font Software.
PERMISSION & CONDITIONS
Permission is hereby granted, free of charge, to any person obtaining
a copy of the Font Software, to use, study, copy, merge, embed, modify,
redistribute, and sell modified and unmodified copies of the Font
Software, subject to the following conditions:
1) Neither the Font Software nor any of its individual components,
in Original or Modified Versions, may be sold by itself.
2) Original or Modified Versions of the Font Software may be bundled,
redistributed and/or sold with any software, provided that each copy
contains the above copyright notice and this license. These can be
included either as stand-alone text files, human-readable headers or
in the appropriate machine-readable metadata fields within text or
binary files as long as those fields can be easily viewed by the user.
3) No Modified Version of the Font Software may use the Reserved Font
Name(s) unless explicit written permission is granted by the corresponding
Copyright Holder. This restriction only applies to the primary font name as
presented to the users.
4) The name(s) of the Copyright Holder(s) or the Author(s) of the Font
Software shall not be used to promote, endorse or advertise any
Modified Version, except to acknowledge the contribution(s) of the
Copyright Holder(s) and the Author(s) or with their explicit written
permission.
5) The Font Software, modified or unmodified, in part or in whole,
must be distributed entirely under this license, and must not be
distributed under any other license. The requirement for fonts to
remain under this license does not apply to any document created
using the Font Software.
TERMINATION
This license becomes null and void if any of the above conditions are
not met.
DISCLAIMER
THE FONT SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO ANY WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT
OF COPYRIGHT, PATENT, TRADEMARK, OR OTHER RIGHT. IN NO EVENT SHALL THE
COPYRIGHT HOLDER BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
INCLUDING ANY GENERAL, SPECIAL, INDIRECT, INCIDENTAL, OR CONSEQUENTIAL
DAMAGES, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
FROM, OUT OF THE USE OR INABILITY TO USE THE FONT SOFTWARE OR FROM
OTHER DEALINGS IN THE FONT SOFTWARE.
/// _base
//
// Description:
// - Implement Design Guide specifications as applicable
// - Provide helper/shorthand mixins
// - Provide attribute sets for reuse
//
// Contents:
// - Colors
// - Typography
// - Helper mixins
// - Pattern mixins
///
/// Bootstrap paths and variables
@import env
///------------------------------------------------------------------------///
// Colors //
///------------------------------------------------------------------------///
/// Palette
Design Guide colors
$dark-blue: #13628e
$bright-blue: #c5f1fd
$text-blue: #47cfec
$gold: #f9ff68
$white: #ffffff
$green: #4ebf7a
$green-shade: #44a76b
$red: #d66a6a
/// Alerts
Text and background colors
$alert__fg--success: darken($green, 10%)
$alert__bg--success: lighten($green, 35%)
$alert__fg--info: darken($text-blue, 15%)
$alert__bg--info: lighten($bright-blue, 5%)
$alert__fg--warning: darken($gold, 35%)
$alert__fg--warning: lighten($gold, 15%)
$alert__fg--danger: darken($red, 15%)
$alert__bg--danger: lighten($red, 20%)
/// Typography
Text in various contexts
$link--normal: $green-shade // link, normal
$link--hover: #378756 // link, hover (unconfirmed)
$link--visited: $green-shade // link, visited (unknown)
///------------------------------------------------------------------------///
// Typography //
///------------------------------------------------------------------------///
/// Set font attributes
Parameters: family, style, weight, font path
=font-init($family, $weight, $style, $woff2, $woff)
@font-face
font-family: $family
font-weight: $weight
font-style: $style
font-display: auto
src: url($woff2) format("woff2"), url($woff) format("woff")
/// Font-face presets
Presets are called as needed. Subsites may use all or a few presets.
// Light
=nunito--300
+font-init("Nunito", 300, normal, $nunito-300, $nunito-300-woff)
=nunito--300i
+font-init("Nunito", 300, italic, $nunito-300i, $nunito-300i-woff)
// Regular
=nunito--400
+font-init("Nunito", 400, normal, $nunito-400, $nunito-400-woff)
=nunito--400i
+font-init("Nunito", 400, italic, $nunito-400i, $nunito-400i-woff)
// SemiBold
=nunito--600
+font-init("Nunito", 600, normal, $nunito-600, $nunito-600-woff)
=nunito--600i
+font-init("Nunito", 600, italic, $nunito-600i, $nunito-600i-woff)
// Bold
=nunito--700
+font-init("Nunito", 700, normal, $nunito-700, $nunito-700-woff)
=nunito--700i
+font-init("Nunito", 700, italic, $nunito-700, $nunito-700i-woff)
// ExtraBold
=nunito--800
+font-init("Nunito", 800, normal, $nunito-800, $nunito-800-woff)
=nunito--800i
+font-init("Nunito", 800, italic, $nunito-800i, $nunito-800i-woff)
/// Text sizes from the Design Guide v. 2017-06-23
Based on html font size at 62.5% of 16px browser font size
=text--title
font: 700 3.7rem/1 Nunito, sans-serif
=text--headline
font: 700 2.9rem/3rem Nunito, sans-serif
=text--body
font: 300 2.1rem/3rem Nunito, sans-serif
=text--link
font: 400 2.1rem/3rem Nunito, sans-serif
color: $link--normal
text-decoration: none
cursor: pointer
transition: all 0.15s ease-in 0s
&:hover, &:active, &:focus
color: $link--hover
&:visited
color: $link--visited
=text--small
font: 400 1.7rem/3rem Nunito, sans-serif
=text--smallest
font: 600 1.3rem/3rem Nunito, sans-serif
///------------------------------------------------------------------------///
// Helper mixins //
///------------------------------------------------------------------------///
/// Global border-box preset
Clear all elements of margin and padding. A non-zero default is
inapplicable to most cases and interferes with layout calculations.
Border-box helps contain margin/padding changes to the specified
element.
=star--border-box
*
margin: 0
padding: 0
box-sizing: border-box
/// html font-size preset
Set font size to 62.5% of 16px browser default, i.e. 1rem = 10px.
=html--10px
html
font-size: 62.5%
=html--8px
html
font-size: 50%
/// Insert background attribute
Parameters: image path, background position/size
Passing "center" for $args will output a centered, no-repeat preset.
=bg($img, $args)
@if $args == "center"
background: url($img) center center no-repeat
@else
background: url($img) + " #{$args}"
/// Like =bg, but for non-repeating images with size attributes.
Parameters: image path, width height, bg position/bg size
e.g. +image($project-logo, 19.2rem 13.2rem, center/contain)
=image($img, $size, $args)
background: url($img) + " #{$args} no-repeat"
width: nth($size, 1)
height: nth($size, 2)
/// Hide elements visually
=hidden
width: 1px
height: 1px
position: absolute
overflow: hidden
clip: rect(0 0 0 0)
margin: -1px
padding: 0
border: 0
/// Insert sprite background image
Parameters: image, background position x y, size width height
e.g. +sprite("/path/to/sprites.svg", 0 0, 3.8rem 4rem)
=sprite($file, $bgpos, $size)
background: url($file) no-repeat $bgpos
width: nth($size, 1)
height: nth($size, 2)
///------------------------------------------------------------------------///
// Pattern mixins //
///------------------------------------------------------------------------///
/// Set button styles
Parameters: text color, bg gradient color 1, bg gradient color 2
These are to be used on <a> tag to create a button appearance.
/// +button--base() only provides base styling for making buttons of different
sizes, and needs a few more attributes to make a complete button. See the
other button-- mixins like +button--sml for examples.
/// The buttons should also be used with a min-width attribute in normal and
:hover state, for a button resize effect on hover. min-width is not set
here, as the value depends on the length of the link text and the parent
element width. Example:
/// .link--example
+button--sml($white, $green, $green-shade)
min-width: 21rem
&:hover
min-width: 18rem
=button--base($tc, $bc1, $bc2)
$text-shadow1: 0.1rem 0.1rem 0 transparentize($dark-blue, 0.6)
$text-shadow2: 0rem 0rem 1rem transparentize($white, 0.5)
font-weight: 800
line-height: 1
font-family: Nunito, sans-serif
text-align: center
text-decoration: none
text-shadow: 0.2rem 0.2rem 0 darken($bc2, 5%)
color: $tc
background-color: $bc2
background-image: linear-gradient($bc1, $bc2)
cursor: pointer
display: table
position: relative
width: auto
border: 0
border-radius: 0.6rem/3rem
box-shadow: 0.3rem 0.3rem 0.3rem 0.3rem transparentize($dark-blue, 0.9)
transition: all 0.12s ease-out
&:hover
color: $tc
text-shadow: $text-shadow1, $text-shadow2
=button--big($tc, $bc1, $bc2)
+button--base($tc, $bc1, $bc2)
font-size: 2.1rem
padding: 0.9rem 12rem 1.2rem
&:hover
padding: 0.9rem 6rem 1.2rem
=button--medium($tc, $bc1, $bc2)
+button--base($tc, $bc1, $bc2)
font-size: 2.1rem
padding: 0.9rem 6rem 1.2rem
&:hover
padding: 0.9rem 3rem 1.2rem
=button--small($tc, $bc1, $bc2)
+button--base($tc, $bc1, $bc2)
font-size: 1.7rem
padding: 0.6rem 3rem 0.9rem
&:hover
padding: 0.6rem 1.5rem 0.9rem
/// Add a tilted baseline effect to a header
Parameters: element width (vw), element height (rem),
vertical shift (rem), skew amount (deg)
=header-tilt($width, $height, $vshift, $skew)
position: relative
clear: both
margin: 0
z-index: -1
&:after
content: " "
position: absolute
width: $width
height: $height
bottom: $vshift
transform: skewY($skew)
z-index: -1
/// Set horizontal rule line to tilt upward ltr
=hr-tilt
height: 0.15rem
margin: 3rem 0
transform: skewY(-3deg)
background: $bright-blue none repeat scroll 0 0
border: 0
border-radius: 0.3rem
/// _content
//
// Description: set up common page elements accompanying the template.
//
///
.header.m--tilt
+bg($header-bg-flbk, center bottom/cover no-repeat)
+text--title
color: $dark-blue
text-align: center
margin: 0 0 3rem 0
+respond("xs")
.header.m--tilt
padding: 10.5rem 0 6rem
+respond("s")
.header.m--tilt
padding: 10.5rem 0 6rem
+respond("m")
.header.m--tilt
padding: 15rem 0 9rem
/// _env
//
// Description: define Sass path variables.
//
///
/// Fonts
$nunito-300: @{StaticR fonts_nunito_nunito_light_woff2}
$nunito-300i: @{StaticR fonts_nunito_nunito_lightitalic_woff2}
$nunito-300-woff: @{StaticR fonts_nunito_nunito_light_woff}
$nunito-300i_woff: @{StaticR fonts_nunito_nunito_lightitalic_woff}
$nunito-400: @{StaticR fonts_nunito_nunito_regular_woff2}
$nunito-400i: @{StaticR fonts_nunito_nunito_italic_woff2}
$nunito-400-woff: @{StaticR fonts_nunito_nunito_regular_woff}
$nunito-400i_woff: @{StaticR fonts_nunito_nunito_italic_woff}
$nunito-600: @{StaticR fonts_nunito_nunito_semibold_woff2}
$nunito-600i: @{StaticR fonts_nunito_nunito_semibolditalic_woff2}
$nunito-600-woff: @{StaticR fonts_nunito_nunito_semibold_woff}
$nunito-600i_woff: @{StaticR fonts_nunito_nunito_semibolditalic_woff}
$nunito-700: @{StaticR fonts_nunito_nunito_bold_woff2}
$nunito-700i: @{StaticR fonts_nunito_nunito_bolditalic_woff2}
$nunito-700-woff: @{StaticR fonts_nunito_nunito_bold_woff}
$nunito-700i_woff: @{StaticR fonts_nunito_nunito_bolditalic_woff}
$nunito-800: @{StaticR fonts_nunito_nunito_extrabold_woff2}
$nunito-800i: @{StaticR fonts_nunito_nunito_extrabolditalic_woff2}
$nunito-800-woff: @{StaticR fonts_nunito_nunito_extrabold_woff}
$nunito-800i_woff: @{StaticR fonts_nunito_nunito_extrabolditalic_woff}
/// Images: main
$navbar_logo: @{StaticR img_main_navbar_logo_png}
$navbar_menu_arrow: @{StaticR img_main_navbar_menu_arrow_png}
$navbar_menu_open: @{StaticR img_main_navbar_menu_open_png}
$navbar_menu_close: @{StaticR img_main_navbar_menu_close_png}
$navbar_menu_user: @{StaticR img_main_navbar_menu_user_png}
$header_bg_flbk: @{StaticR img_main_header_bg_flbk_png}
$footer_logo: @{StaticR img_main_footer_logo_png}
$footer_bg_flbk: @{StaticR img_main_footer_bg_flbk_png}
$partner_osi: @{StaticR img_main_partner_osi_png}
$partner_qco: @{StaticR img_main_partner_qco_png}
$partner_oin: @{StaticR img_main_partner_oin_png}
$partner_cfi: @{StaticR img_main_partner_cfi_png}
/// Images: projects
$project_logo: @{StaticR img_projects_project_logo_png}
$me_signup: @{StaticR img_projects_me_signup_png}
/// Images: how-it-works
$caption_arrow: @{StaticR img_how_it_works_caption_arrow_png}
/// _footer
//
// Description: styles for the footer partial.
//
///
$p: ".footer"
#{$p}
+pad($grid--s, mgn 5 0 0 0)
#{$p} .logo
+image($footer-logo, 100% 4.4rem, center/contain)
#{$p} .link
+text--link
/* Nav */
#{$p} .menu
list-style: none
.link
font-weight: 600
#{$p} .licenses
+text--body
font-weight: 400
color: $text-blue
/* Partners */
#{$p} .partners
/* Fallback gradient bg image for older mobile browsers */
+bg($footer-bg-flbk, center/contain repeat-x)
color: $text-blue
list-style: none
/* Partners heading */
#{$p} .partners-header
+text--body
font-weight: 400
color: $text-blue
text-align: center
/* Partner links: opacity change on hover */
#{$p} .link
&.m--osi, &.m--qco, &.m--oin, &.m--cfi
opacity: 0.7
&:hover
opacity: 1.0
/* Partner logos */
#{$p} .osi
+image($partner-osi, 100% 6rem, center/contain)
#{$p} .qco
+image($partner-qco, 100% 6rem, center/contain)
#{$p} .oin
+image($partner-oin, 100% 6rem, center/contain)
#{$p} .cfi
+image($partner-cfi, 100% 6rem, center/contain)
///------------------------------------------------------------------------///
// Extra small //
///------------------------------------------------------------------------///
+respond("xs")
#{$p} .link.m--logo
+vr($grid--s, mgn 1)
+pad($grid--s, pad 0 1)
/* Nav */
#{$p} nav
+pad($grid--s, mgn 1 1 2)
#{$p} .menu
+span($grid--s, 2 max auto)
+vr($grid--s, mgn 1)
/* Partners */
#{$p} .partners
+pad($grid--s, pad 1 1)
#{$p} .partners-header
+pad($grid--s, mgn 1 auto)
#{$p} .link
&.m--osi, &.m--qco, &.m--oin, &.m--cfi
+span($grid--s, 3 max auto)
+vr($grid--s, 1)
///------------------------------------------------------------------------///
// Small //
///------------------------------------------------------------------------///
+respond("s")
#{$p} .link.m--logo
+span($grid--s, 4 auto auto)
+vr($grid--s, mgn 1)
#{$p} nav
+box(center)
+vr($grid--s, mgn 2)
#{$p} .menu
&.m--1-2
+span($grid--s, 2 auto both)
&.m--2-2
+span($grid--s, 2 auto right)
#{$p} .licenses
+span($grid--s, 4 auto both)
+vr($grid--s, mgn 2 1)
#{$p} .partners
+vr($grid--s, pad 2 1)
#{$p} .partners-header
+vr($grid--s, mgn 1)
#{$p} .partners .row
+box(center)
+vr($grid--s, mgn 2 0)
&.m--1-2
margin: 0
#{$p} .link
&.m--osi, &.m--oin
+span($grid--s, 2 auto both)
&.m--qco, &.m--cfi
+span($grid--s, 2 auto right)
///------------------------------------------------------------------------///
// Medium //
///------------------------------------------------------------------------///
+respond("m")
#{$p} .logo
+span($grid--m, 4 max)
margin: auto
#{$p} .link.m--logo
+vr($grid--m, mgn 1)
#{$p} nav
+box(space-between)
+span($grid--m, 8 row auto)
+vr($grid--m, mgn 2 3)
#{$p} .menu
&.m--1-2
+span($grid--m, 2 auto both)
&.m--2-2
+span($grid--m, 2 auto right)
#{$p} .licenses
+flex($grid--m, 4)
#{$p} .partners
+box(center)
+vr($grid--m, pad 2)
#{$p} .partners-header
+vr($grid--m, mgn 3 1)
#{$p} .partners .row
display: flex
&.m--1-2
+span($grid--m, 4 auto both)
&.m--2-2
+span($grid--m, 4 auto right)
#{$p} .link
&.m--osi, &.m--qco, &.m--oin, &.m--cfi
+span($grid--m, 2)
flex: auto
&.m--osi, &.m--oin
+span($grid--m, 2 auto right)
/// main.sass
//
// Description: the template wrapper used by most pages.
//
///
///------------------------------------------------------------------------///
// Imports: base //
///------------------------------------------------------------------------///
/// - base: Design Guide elements
- mikuro: grid mixins
@import base
@import mikuro
///------------------------------------------------------------------------///
// Grid settings and media queries //
///------------------------------------------------------------------------///
/// Grid settings
$grid--s: (container: 39rem, cols: 4, gutter: 3rem, v-rhythm: 3rem)
$grid--m: (container: 75rem, cols: 8, gutter: 3rem, v-rhythm: 3rem)
/// Convert container width from number to string for media queries
$view--s: num-string(map-get($grid--s, "container"), "px")
$view--s-1: num-string((map-get($grid--s, "container") - 0.1), "px")
$view--m: num-string(map-get($grid--m, "container"), "px")
$view--m-1: num-string((map-get($grid--m, "container") - 0.1), "px")
/// Media query mixin
=respond($view)
@if $view == "xs"
@media (max-width: #{$view--s-1})
@content
@else if $view == "s"
@media (min-width: #{$view--s}) and (max-width: #{$view--m-1})
@content
@else if $view == "m"
@media (min-width: #{$view--m})
@content
///------------------------------------------------------------------------///
// Init @font-face and global selectors //
///------------------------------------------------------------------------///
/* @font-face */
+nunito--300 // text--body
+nunito--300i
+nunito--400 // text--link
+nunito--400i
+nunito--700 // text--headline, text--title
+nunito--700i
+nunito--800 // button--*
/* Global selector: zero-margin/padding border-box preset */
+star--border-box
/* Font scaling */
+respond("xs")
/* html: 1rem = 8px */
+html--8px
+respond("s")
/* html: 1rem = 10px */
+html--10px
+respond("m")
+html--10px
///------------------------------------------------------------------------///
// Preset modifiers //
///------------------------------------------------------------------------///
/* Links nesting div elements to inherit the correct height */
.m--div
display: block
///------------------------------------------------------------------------///
// Imports: partials //
///------------------------------------------------------------------------///
@import _navbar
@import _content
@import _footer
///------------------------------------------------------------------------///
// Development mode //
///------------------------------------------------------------------------///
/// Skip this section for production.