diff --git a/client/assets/images/at-the-party/at-the.svg b/client/assets/images/at-the-party/at-the.svg
new file mode 100644
index 0000000..97fa625
--- /dev/null
+++ b/client/assets/images/at-the-party/at-the.svg
@@ -0,0 +1,21 @@
+
diff --git a/client/assets/images/at-the-party/lines.svg b/client/assets/images/at-the-party/lines.svg
new file mode 100644
index 0000000..4bcee81
--- /dev/null
+++ b/client/assets/images/at-the-party/lines.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/client/assets/images/at-the-party/party.svg b/client/assets/images/at-the-party/party.svg
new file mode 100644
index 0000000..269a34b
--- /dev/null
+++ b/client/assets/images/at-the-party/party.svg
@@ -0,0 +1,21 @@
+
diff --git a/client/src/elm/MassiveDecks.elm b/client/src/elm/MassiveDecks.elm
index 0d3366d..6ff3317 100644
--- a/client/src/elm/MassiveDecks.elm
+++ b/client/src/elm/MassiveDecks.elm
@@ -88,7 +88,9 @@ init flags url key =
, speech = speech
, notifications = Notifications.init
, remoteMode = remoteMode
+ , serverVersion = Nothing
, sources = { builtIn = Nothing, manyDecks = Nothing, jsonAgainstHumanity = Nothing }
+ , adverts = { manyDecks = False, atTheParty = False }
}
( page, pageCmd ) =
@@ -99,7 +101,7 @@ init flags url key =
( Pages.Loading, Cmd.none )
sourceCmd =
- Request.map (Error.Add >> ErrorMsg) never UpdateSources |> Api.sourceInfo |> Http.request
+ Request.map (Error.Add >> ErrorMsg) never UpdateServerConfig |> Api.serverConfig |> Http.request
in
( { page = page, shared = shared, errorOverlay = Overlay.init }
, Cmd.batch [ sourceCmd, pageCmd, settingsCmd, speechCmd ]
@@ -271,12 +273,21 @@ update msg model =
in
( { model | shared = { oldShared | notifications = notifications } }, notificationsCmd )
- UpdateSources info ->
+ UpdateServerConfig { serverVersion, sourceInfo, adverts } ->
let
oldShared =
model.shared
in
- ( { model | shared = { oldShared | sources = info } }, Cmd.none )
+ ( { model
+ | shared =
+ { oldShared
+ | serverVersion = Just serverVersion
+ , sources = sourceInfo
+ , adverts = adverts
+ }
+ }
+ , Cmd.none
+ )
Refresh ->
( model, Navigation.reload )
diff --git a/client/src/elm/MassiveDecks/Messages.elm b/client/src/elm/MassiveDecks/Messages.elm
index 2d43ff1..395bc5f 100644
--- a/client/src/elm/MassiveDecks/Messages.elm
+++ b/client/src/elm/MassiveDecks/Messages.elm
@@ -1,6 +1,5 @@
module MassiveDecks.Messages exposing (Msg(..))
-import MassiveDecks.Card.Source.Model as Source
import MassiveDecks.Cast.Model as Cast
import MassiveDecks.Error.Messages as Error
import MassiveDecks.Notifications.Model as Notifications
@@ -8,6 +7,7 @@ import MassiveDecks.Pages.Lobby.Messages as Lobby
import MassiveDecks.Pages.Lobby.Model as Lobby
import MassiveDecks.Pages.Route exposing (Route)
import MassiveDecks.Pages.Start.Messages as Start
+import MassiveDecks.ServerConfig exposing (ServerConfig)
import MassiveDecks.Settings.Messages as Settings
import MassiveDecks.Speech as Speech
import Url exposing (Url)
@@ -28,6 +28,6 @@ type Msg
| UpdateToken Lobby.Auth
| CastStatusUpdate Cast.Status
| RemoteCommand Cast.RemoteControlCommand
- | UpdateSources Source.Info
+ | UpdateServerConfig ServerConfig
| Refresh
| BlockedExternalUrl
diff --git a/client/src/elm/MassiveDecks/Model.elm b/client/src/elm/MassiveDecks/Model.elm
index b1cb968..3f0cb5e 100644
--- a/client/src/elm/MassiveDecks/Model.elm
+++ b/client/src/elm/MassiveDecks/Model.elm
@@ -7,6 +7,7 @@ import Browser.Navigation as Navigation
import MassiveDecks.Card.Source.Model as Sources
import MassiveDecks.Cast.Model as Cast
import MassiveDecks.Notifications.Model as Notifications
+import MassiveDecks.ServerConfig exposing (Adverts)
import MassiveDecks.Settings.Model as Settings exposing (Settings)
import MassiveDecks.Speech as Speech
import MassiveDecks.Strings.Languages.Model exposing (Language)
@@ -24,7 +25,9 @@ type alias Shared =
, speech : Speech.Model
, notifications : Notifications.Model
, remoteMode : Bool
+ , serverVersion : Maybe String
, sources : Sources.Info
+ , adverts : Adverts
}
diff --git a/client/src/elm/MassiveDecks/Models/Decoders.elm b/client/src/elm/MassiveDecks/Models/Decoders.elm
index b8593a1..9f8394d 100644
--- a/client/src/elm/MassiveDecks/Models/Decoders.elm
+++ b/client/src/elm/MassiveDecks/Models/Decoders.elm
@@ -16,6 +16,7 @@ module MassiveDecks.Models.Decoders exposing
, privilege
, remoteControlCommand
, revealingRound
+ , serverConfig
, settings
, sourceInfo
, specificRound
@@ -53,6 +54,7 @@ import MassiveDecks.Pages.Lobby.Events as Events exposing (Event)
import MassiveDecks.Pages.Lobby.GameCode as GameCode exposing (GameCode)
import MassiveDecks.Pages.Lobby.Model as Lobby exposing (Lobby)
import MassiveDecks.Pages.Start.LobbyBrowser.Model as LobbyBrowser
+import MassiveDecks.ServerConfig as ServerConfig exposing (ServerConfig)
import MassiveDecks.Settings.Model as Settings exposing (Settings)
import MassiveDecks.Speech as Speech
import MassiveDecks.Strings.Languages as Lang
@@ -62,6 +64,13 @@ import MassiveDecks.Util.NeList as NonEmptyList
import Set exposing (Set)
+adverts : Json.Decoder ServerConfig.Adverts
+adverts =
+ Json.succeed ServerConfig.Adverts
+ |> Json.optional "manyDecks" Json.bool False
+ |> Json.optional "atTheParty" Json.bool False
+
+
sourceInfo : Json.Decoder Source.Info
sourceInfo =
let
@@ -79,6 +88,14 @@ sourceInfo =
|> Json.andThen atLeastOne
+serverConfig : Json.Decoder ServerConfig
+serverConfig =
+ Json.succeed ServerConfig
+ |> Json.required "version" Json.string
+ |> Json.required "sources" sourceInfo
+ |> Json.optional "adverts" adverts { manyDecks = False, atTheParty = False }
+
+
builtInInfo : Json.Decoder BuiltIn.Info
builtInInfo =
Json.succeed BuiltIn.Info
diff --git a/client/src/elm/MassiveDecks/Pages/Start.elm b/client/src/elm/MassiveDecks/Pages/Start.elm
index 1868844..60b1493 100644
--- a/client/src/elm/MassiveDecks/Pages/Start.elm
+++ b/client/src/elm/MassiveDecks/Pages/Start.elm
@@ -200,13 +200,34 @@ view shared model =
model.route
manyDecksAd { baseUrl } =
- Html.blankA
- [ HtmlA.href baseUrl, HtmlA.id "many-decks-ad", Strings.ManyDecksWhereToGet |> Lang.title shared ]
- [ Html.div []
- [ Icon.boxOpen |> Icon.viewIcon
- , Html.span [] [ Strings.ManyDecks |> Lang.string shared |> Html.text ]
+ if shared.adverts.manyDecks then
+ Html.blankA
+ [ HtmlA.href baseUrl, HtmlA.id "many-decks-ad", Strings.ManyDecksWhereToGet |> Lang.title shared ]
+ [ Html.div []
+ [ Icon.boxOpen |> Icon.viewIcon
+ , Html.span [] [ Strings.ManyDecks |> Lang.string shared |> Html.text ]
+ ]
]
- ]
+
+ else
+ Html.nothing
+
+ atThePartyAd =
+ if shared.adverts.atTheParty then
+ Html.div [ HtmlA.id "at-the-party-ad" ]
+ [ Html.div []
+ [ Html.blankA
+ [ HtmlA.href "https://store.steampowered.com/app/1506530/At_The_Party"
+ , HtmlA.title "Check out Reread Game's new game, At The Party!"
+ ]
+ [ Html.div [ HtmlA.class "at-the" ] [ Html.span [] [ Html.text "At The" ] ]
+ , Html.div [ HtmlA.class "party" ] [ Html.span [] [ Html.text "Party" ] ]
+ ]
+ ]
+ ]
+
+ else
+ Html.nothing
in
[ Html.div [ HtmlA.class "page start" ]
[ overlay shared model.overlay
@@ -247,11 +268,12 @@ view shared model =
[ Html.text "\""
, Strings.MassiveDecks |> Lang.html shared
, Html.text "\" "
- , Strings.Version { versionNumber = Version.version } |> Lang.html shared
+ , Strings.Version { clientVersion = Version.version, serverVersion = shared.serverVersion } |> Lang.html shared
]
]
]
, shared.sources.manyDecks |> Maybe.map manyDecksAd |> Maybe.withDefault Html.nothing
+ , atThePartyAd
]
diff --git a/client/src/elm/MassiveDecks/Requests/Api.elm b/client/src/elm/MassiveDecks/Requests/Api.elm
index 83d8543..c3316be 100644
--- a/client/src/elm/MassiveDecks/Requests/Api.elm
+++ b/client/src/elm/MassiveDecks/Requests/Api.elm
@@ -3,7 +3,7 @@ module MassiveDecks.Requests.Api exposing
, joinLobby
, lobbySummaries
, newLobby
- , sourceInfo
+ , serverConfig
)
import Http
@@ -19,6 +19,7 @@ import MassiveDecks.Pages.Lobby.Token as Token
import MassiveDecks.Pages.Start.LobbyBrowser.Model as LobbyBrowser
import MassiveDecks.Pages.Start.Model as Start
import MassiveDecks.Requests.Request as Request exposing (Request)
+import MassiveDecks.ServerConfig exposing (ServerConfig)
import MassiveDecks.User as User
import MassiveDecks.Util.Result as Result
import Url.Builder
@@ -80,15 +81,15 @@ checkAlive msg tokens =
}
-{-| Find out what sources the server offers.
+{-| Get configuration from the server.
-}
-sourceInfo : (Request.Response Never Source.Info -> msg) -> Request msg
-sourceInfo msg =
+serverConfig : (Request.Response Never ServerConfig -> msg) -> Request msg
+serverConfig msg =
{ method = "GET"
, headers = []
- , url = url [ "sources" ]
+ , url = url [ "config" ]
, body = Http.emptyBody
- , expect = Request.expectResponse msg noError Decoders.sourceInfo
+ , expect = Request.expectResponse msg noError Decoders.serverConfig
, timeout = Nothing
, tracker = Nothing
}
diff --git a/client/src/elm/MassiveDecks/ServerConfig.elm b/client/src/elm/MassiveDecks/ServerConfig.elm
new file mode 100644
index 0000000..4557d3a
--- /dev/null
+++ b/client/src/elm/MassiveDecks/ServerConfig.elm
@@ -0,0 +1,19 @@
+module MassiveDecks.ServerConfig exposing
+ ( Adverts
+ , ServerConfig
+ )
+
+import MassiveDecks.Card.Source.Model as Source
+
+
+type alias Adverts =
+ { manyDecks : Bool
+ , atTheParty : Bool
+ }
+
+
+type alias ServerConfig =
+ { serverVersion : String
+ , sourceInfo : Source.Info
+ , adverts : Adverts
+ }
diff --git a/client/src/elm/MassiveDecks/Strings.elm b/client/src/elm/MassiveDecks/Strings.elm
index 90c037e..9d295a0 100644
--- a/client/src/elm/MassiveDecks/Strings.elm
+++ b/client/src/elm/MassiveDecks/Strings.elm
@@ -54,7 +54,7 @@ type MdString
-- Special
| Noun { noun : Noun, quantity : Quantity } -- The given noun, described in the given quantity and context.
-- Start screen.
- | Version { versionNumber : String } -- The version of the game being played.
+ | Version { clientVersion : String, serverVersion : Maybe String } -- The version of the game being played.
| ShortGameDescription -- A one-line description of the game.
| WhatIsThis -- A title for a section describing the game.
| GameDescription -- A long description of the game.
diff --git a/client/src/elm/MassiveDecks/Strings/Languages/De.elm b/client/src/elm/MassiveDecks/Strings/Languages/De.elm
index f8e6311..f51be43 100644
--- a/client/src/elm/MassiveDecks/Strings/Languages/De.elm
+++ b/client/src/elm/MassiveDecks/Strings/Languages/De.elm
@@ -92,8 +92,17 @@ translate _ mdString =
[ Text "Zuschauer" ]
-- Start screen.
- Version { versionNumber } ->
- [ Text "Version “", Text versionNumber, Text "”" ]
+ Version { clientVersion, serverVersion } ->
+ let
+ quote version =
+ [ Text "“", Text version, Text "”" ]
+ in
+ List.concat
+ [ [ Text "Version " ]
+ , clientVersion |> quote
+ , [ Text " / " ]
+ , serverVersion |> Maybe.map quote |> Maybe.withDefault []
+ ]
ShortGameDescription ->
[ Text "Ein lustiges Party-Spiel." ]
diff --git a/client/src/elm/MassiveDecks/Strings/Languages/DeXInformal.elm b/client/src/elm/MassiveDecks/Strings/Languages/DeXInformal.elm
index d9c059c..e316499 100644
--- a/client/src/elm/MassiveDecks/Strings/Languages/DeXInformal.elm
+++ b/client/src/elm/MassiveDecks/Strings/Languages/DeXInformal.elm
@@ -91,8 +91,17 @@ translate _ mdString =
[ Text "Zuschauer" ]
-- Start screen.
- Version { versionNumber } ->
- [ Text "Version “", Text versionNumber, Text "”" ]
+ Version { clientVersion, serverVersion } ->
+ let
+ quote version =
+ [ Text "“", Text version, Text "”" ]
+ in
+ List.concat
+ [ [ Text "Version " ]
+ , clientVersion |> quote
+ , [ Text " / " ]
+ , serverVersion |> Maybe.map quote |> Maybe.withDefault []
+ ]
ShortGameDescription ->
[ Text "Ein lustiges Party-Spiel." ]
diff --git a/client/src/elm/MassiveDecks/Strings/Languages/En/Internal.elm b/client/src/elm/MassiveDecks/Strings/Languages/En/Internal.elm
index 91b8fbc..7769e49 100644
--- a/client/src/elm/MassiveDecks/Strings/Languages/En/Internal.elm
+++ b/client/src/elm/MassiveDecks/Strings/Languages/En/Internal.elm
@@ -46,8 +46,17 @@ translate _ mdString =
List.concat [ singular, plural ]
-- Start screen.
- Version { versionNumber } ->
- [ Text "Version “", Text versionNumber, Text "”" ]
+ Version { clientVersion, serverVersion } ->
+ let
+ quote version =
+ [ Text "“", Text version, Text "”" ]
+ in
+ List.concat
+ [ [ Text "Version " ]
+ , clientVersion |> quote
+ , [ Text " / " ]
+ , serverVersion |> Maybe.map quote |> Maybe.withDefault []
+ ]
ShortGameDescription ->
[ Text "A comedy party game." ]
diff --git a/client/src/elm/MassiveDecks/Strings/Languages/Es.elm b/client/src/elm/MassiveDecks/Strings/Languages/Es.elm
index eed068b..fde423e 100644
--- a/client/src/elm/MassiveDecks/Strings/Languages/Es.elm
+++ b/client/src/elm/MassiveDecks/Strings/Languages/Es.elm
@@ -68,8 +68,17 @@ translate _ mdString =
List.concat [ singular, plural ]
-- Start screen.
- Version { versionNumber } ->
- [ Text "Versión “", Text versionNumber, Text "”" ]
+ Version { clientVersion, serverVersion } ->
+ let
+ quote version =
+ [ Text "“", Text version, Text "”" ]
+ in
+ List.concat
+ [ [ Text "Versión " ]
+ , clientVersion |> quote
+ , [ Text " / " ]
+ , serverVersion |> Maybe.map quote |> Maybe.withDefault []
+ ]
ShortGameDescription ->
[ Text "Un juego para fiestas de comedia." ]
diff --git a/client/src/elm/MassiveDecks/Strings/Languages/Id.elm b/client/src/elm/MassiveDecks/Strings/Languages/Id.elm
index 67e4b1b..f368106 100644
--- a/client/src/elm/MassiveDecks/Strings/Languages/Id.elm
+++ b/client/src/elm/MassiveDecks/Strings/Languages/Id.elm
@@ -81,8 +81,17 @@ translate _ mdString =
List.concat [ singular, plural ]
-- Start screen.
- Version { versionNumber } ->
- [ Text "Versi “", Text versionNumber, Text "”" ]
+ Version { clientVersion, serverVersion } ->
+ let
+ quote version =
+ [ Text "“", Text version, Text "”" ]
+ in
+ List.concat
+ [ [ Text "Versi " ]
+ , clientVersion |> quote
+ , [ Text " / " ]
+ , serverVersion |> Maybe.map quote |> Maybe.withDefault []
+ ]
ShortGameDescription ->
[ Text "Sebuah permainan pesta komedi." ]
diff --git a/client/src/elm/MassiveDecks/Strings/Languages/It.elm b/client/src/elm/MassiveDecks/Strings/Languages/It.elm
index c3889a8..a24d083 100644
--- a/client/src/elm/MassiveDecks/Strings/Languages/It.elm
+++ b/client/src/elm/MassiveDecks/Strings/Languages/It.elm
@@ -91,8 +91,17 @@ translate _ mdString =
[ Text "Spettatori" ]
-- Start screen.
- Version { versionNumber } ->
- [ Text "Versione “", Text versionNumber, Text "”" ]
+ Version { clientVersion, serverVersion } ->
+ let
+ quote version =
+ [ Text "“", Text version, Text "”" ]
+ in
+ List.concat
+ [ [ Text "Versione " ]
+ , clientVersion |> quote
+ , [ Text " / " ]
+ , serverVersion |> Maybe.map quote |> Maybe.withDefault []
+ ]
ShortGameDescription ->
[ Text "Un divertente gioco di società." ]
diff --git a/client/src/elm/MassiveDecks/Strings/Languages/Pl.elm b/client/src/elm/MassiveDecks/Strings/Languages/Pl.elm
index bd67805..943fae7 100644
--- a/client/src/elm/MassiveDecks/Strings/Languages/Pl.elm
+++ b/client/src/elm/MassiveDecks/Strings/Languages/Pl.elm
@@ -67,8 +67,17 @@ translate maybeDeclCase mdString =
[ Text (decl noun quantity declCase) ]
-- Start screen.
- Version { versionNumber } ->
- [ Text "Wersja „", Text versionNumber, Text "”" ]
+ Version { clientVersion, serverVersion } ->
+ let
+ quote version =
+ [ Text "„", Text version, Text "”" ]
+ in
+ List.concat
+ [ [ Text "Wersja " ]
+ , clientVersion |> quote
+ , [ Text " / " ]
+ , serverVersion |> Maybe.map quote |> Maybe.withDefault []
+ ]
ShortGameDescription ->
[ Text "Komediowa gra towarzyska." ]
diff --git a/client/src/elm/MassiveDecks/Strings/Languages/PtBR.elm b/client/src/elm/MassiveDecks/Strings/Languages/PtBR.elm
index 80491b5..1ed21e6 100644
--- a/client/src/elm/MassiveDecks/Strings/Languages/PtBR.elm
+++ b/client/src/elm/MassiveDecks/Strings/Languages/PtBR.elm
@@ -91,8 +91,17 @@ translate _ mdString =
[ Text "Espectadores" ]
-- Start screen.
- Version { versionNumber } ->
- [ Text "Versão “", Text versionNumber, Text "”" ]
+ Version { clientVersion, serverVersion } ->
+ let
+ quote version =
+ [ Text "“", Text version, Text "”" ]
+ in
+ List.concat
+ [ [ Text "Versão " ]
+ , clientVersion |> quote
+ , [ Text " / " ]
+ , serverVersion |> Maybe.map quote |> Maybe.withDefault []
+ ]
ShortGameDescription ->
[ Text "Um jogo de festa de comédia." ]
diff --git a/client/src/scss/_settings.scss b/client/src/scss/_settings.scss
index ab9af48..e1072ce 100644
--- a/client/src/scss/_settings.scss
+++ b/client/src/scss/_settings.scss
@@ -9,6 +9,11 @@
display: flex;
justify-content: flex-end;
z-index: 10;
+ pointer-events: none;
+
+ > div {
+ pointer-events: all;
+ }
}
.settings-panel {
diff --git a/client/src/scss/pages/_start.scss b/client/src/scss/pages/_start.scss
index a54f966..b36fd68 100644
--- a/client/src/scss/pages/_start.scss
+++ b/client/src/scss/pages/_start.scss
@@ -38,7 +38,7 @@
flex-wrap: wrap;
justify-content: center;
overflow: hidden;
- padding: 4em 0 0 2em;
+ padding: 5em 0 0 2em;
h1,
.subtitle {
@@ -177,10 +177,12 @@
}
}
-#many-decks-ad div {
+#many-decks-ad > div {
+ font-size: clamp(0.9rem, 3vw, 1.2rem);
+
position: fixed;
- left: -0.8em;
- top: -3em;
+ left: 0;
+ top: -6.5em;
z-index: 11;
@@ -191,14 +193,11 @@
justify-content: flex-start;
width: 7em;
- height: 7em;
- border-radius: 100%;
+ height: 10em;
+ border-radius: 7em;
transition: left 0.25s, top 0.25s;
- transform: rotate(-15deg);
-
- font-size: 1.2em;
background-color: colors.$many-decks;
color: #ffffff;
@@ -207,7 +206,138 @@
}
&:hover {
- left: -0.7em;
- top: -2.5em;
+ left: 0em;
+ top: -5.5em;
+ }
+}
+
+@keyframes sway {
+ 0% {
+ transform: rotateX(0);
+ }
+ 33% {
+ transform: rotateX(30deg);
+ }
+ 66% {
+ transform: rotateX(-30deg);
+ }
+ 100% {
+ transform: rotateX(0);
+ }
+}
+
+#at-the-party-ad {
+ font-size: clamp(0.25rem, 2vw, 1.25rem);
+
+ > div {
+ position: fixed;
+ right: -5em;
+ top: 2rem;
+
+ transform: rotate(15deg);
+
+ width: 30em;
+ perspective: 20em;
+
+ &::before {
+ content: "";
+ display: block;
+
+ position: absolute;
+ right: -0.4em;
+ top: -0.4em;
+
+ background-image: url(../../../assets/images/at-the-party/lines.svg);
+ background-size: 7em 100%;
+
+ width: 40em;
+ height: 6.5em;
+ box-sizing: border-box;
+
+ z-index: -1;
+ }
+
+ > a {
+ display: block;
+ width: 50%;
+ aspect-ratio: 135 / 53;
+ margin: auto;
+ background: none;
+
+ transition: none;
+
+ position: relative;
+
+ &::before {
+ transform: rotate(-15deg);
+ content: "";
+ display: block;
+
+ position: absolute;
+ right: -10em;
+ top: -2em;
+
+ width: 50em;
+ height: 22em;
+
+ pointer-events: none;
+
+ opacity: 0;
+ transition: opacity 0.8s;
+
+ background-image: url(data:image/svg+xml;base64,CiAgPHN2ZyB4bWxucz0naHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmcnIHdpZHRoPScxMDAlJyBoZWlnaHQ9JzEwMCUnPgogICAgPHJlY3Qgd2lkdGg9JzEwMDAlJyBoZWlnaHQ9JzEwMDAlJyBmaWxsPScjZmZmJy8+CiAgICA8cmVjdCB3aWR0aD0nMTAwJScgaGVpZ2h0PScxMDAlJyBmaWxsPScjZmYwMDAwJy8+CiAgICA8c3ZnIHg9JzUwJScgeT0nMTAlJyBvdmVyZmxvdz0ndmlzaWJsZSc+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDIwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSg0MCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoNjApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDgwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgxMDApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDEyMCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoMTQwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgxNjApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDE4MCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoMjAwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgyMjApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDI0MCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoMjYwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgyODApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDMwMCknLz4KICAgICAgPHJlY3Qgd2lkdGg9JzIwMDAlJyBoZWlnaHQ9JzIwMDAlJyBmaWxsLW9wYWNpdHk9JzAuMScgZmlsbD0nIzAwMDAwMCcgdHJhbnNmb3JtPSdyb3RhdGUoMzIwKScvPgogICAgICA8cmVjdCB3aWR0aD0nMjAwMCUnIGhlaWdodD0nMjAwMCUnIGZpbGwtb3BhY2l0eT0nMC4xJyBmaWxsPScjMDAwMDAwJyB0cmFuc2Zvcm09J3JvdGF0ZSgzNDApJy8+CiAgICAgIDxyZWN0IHdpZHRoPScyMDAwJScgaGVpZ2h0PScyMDAwJScgZmlsbC1vcGFjaXR5PScwLjEnIGZpbGw9JyMwMDAwMDAnIHRyYW5zZm9ybT0ncm90YXRlKDM2MCknLz4KCiAgICA8L3N2Zz4KICA8L3N2Zz4=);
+ background-size: 150% 150%;
+ background-position: 15% 5%;
+ mask-image: radial-gradient(
+ ellipse at top right,
+ #000000 0,
+ #000000 40%,
+ transparent 60%
+ );
+
+ z-index: -10;
+ }
+
+ &:hover::before {
+ opacity: 1;
+ }
+
+ &:hover {
+ background-position: inherit;
+ }
+
+ > div {
+ content: "";
+ background-size: contain;
+
+ position: absolute;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+
+ z-index: 0;
+
+ > span {
+ display: none;
+ }
+
+ animation: sway 4s ease-in-out 0s alternate infinite;
+
+ @media (prefers-reduced-motion) {
+ animation: none;
+ }
+ }
+
+ > .at-the {
+ transform-origin: top center;
+ background-image: url(../../../assets/images/at-the-party/at-the.svg);
+ }
+
+ > .party {
+ background-image: url(../../../assets/images/at-the-party/party.svg);
+ animation-delay: 0.5s;
+ }
+ }
}
}
diff --git a/client/webpack.config.js b/client/webpack.config.js
index baaaf8c..c255650 100644
--- a/client/webpack.config.js
+++ b/client/webpack.config.js
@@ -138,7 +138,7 @@ module.exports = (env, argv) => {
test: /\.(jpg|png|svg)$/,
type: "asset/resource",
generator: {
- filename: "assets/images/[name].[hash].[ext]",
+ filename: "assets/images/[name].[hash][ext]",
},
},
// Font assets.
@@ -146,7 +146,7 @@ module.exports = (env, argv) => {
test: /\.(woff2)$/,
type: "asset/resource",
generator: {
- filename: "assets/fonts/[name].[hash].[ext]",
+ filename: "assets/fonts/[name].[hash][ext]",
},
},
// App manifest.
@@ -155,7 +155,7 @@ module.exports = (env, argv) => {
exclude: [/elm-stuff/, /node_modules/, /dist/],
type: "asset/resource",
generator: {
- filename: "assets/[name].[hash].[ext]",
+ filename: "assets/[name].[hash][ext]",
},
use: [
{
diff --git a/server/config.json5 b/server/config.json5
index 9583a85..eab24f2 100644
--- a/server/config.json5
+++ b/server/config.json5
@@ -173,6 +173,16 @@
checkAfter: "PT5M",
},
+ // Show additional content to users of the application. These are not adverts
+ // you can buy, just related projects to Massive Decks.
+ adverts: {
+ // Show a link to Many Decks for users to build their own decks.
+ // Note this will only work if you have Many Decks set as a source.
+ manyDecks: true,
+ // Show a link to At The Party, Reread Games new game.
+ atTheParty: false,
+ },
+
// A file to touch when the server is started. If null, we won't touch any files.
touchOnStart: null,
// This will be overwritten by the environment variable MD_TOUCH_ON_START, if set.
diff --git a/server/src/ts/config.ts b/server/src/ts/config.ts
index ea15f07..1fc0424 100644
--- a/server/src/ts/config.ts
+++ b/server/src/ts/config.ts
@@ -29,6 +29,7 @@ export interface Config extends EnvironmentalConfig {
storage: BaseStorage;
cache: BaseCache;
defaults: LobbyConfig.Defaults;
+ adverts?: Adverts;
}
export type Parsed = Config;
@@ -121,8 +122,13 @@ export type PostgreSQLCache = BasePostgreSQLCache;
const parseDuration = (unparsed: UnparsedDuration): ParsedDuration =>
Moment.duration(unparsed).asMilliseconds();
+interface Adverts {
+ manyDecks?: boolean;
+ atTheParty?: boolean;
+}
+
export const parseStorage = (
- storage: BaseStorage
+ storage: BaseStorage,
): BaseStorage => ({
...storage,
abandonedTime: parseDuration(storage.abandonedTime),
@@ -130,28 +136,28 @@ export const parseStorage = (
});
export const parseCache = (
- cache: BaseCache
+ cache: BaseCache,
): BaseCache => ({
...cache,
checkAfter: parseDuration(cache.checkAfter),
});
export const parseTimeouts = (
- timeouts: Timeouts
+ timeouts: Timeouts,
): Timeouts =>
Util.mapObjectValues(timeouts, (key: string, value: UnparsedDuration) =>
- parseDuration(value)
+ parseDuration(value),
);
export const parseTasks = (
- tasks: Tasks
+ tasks: Tasks,
): Tasks => ({
...tasks,
processTickFrequency: parseDuration(tasks.processTickFrequency),
});
const parseManyDecks = (
- manyDecks: BaseManyDecks
+ manyDecks: BaseManyDecks,
): ManyDecks => ({
...manyDecks,
baseUrl: manyDecks.baseUrl.endsWith("/")
diff --git a/server/src/ts/index.ts b/server/src/ts/index.ts
index 62bc460..e031d4e 100644
--- a/server/src/ts/index.ts
+++ b/server/src/ts/index.ts
@@ -50,8 +50,8 @@ function getConfigFilePath(): string {
async function main(): Promise {
const config = ServerConfig.parse(
JSON5.parse(
- (await fs.readFile(getConfigFilePath())).toString()
- ) as ServerConfig.Unparsed
+ (await fs.readFile(getConfigFilePath())).toString(),
+ ) as ServerConfig.Unparsed,
);
const { app } = ws(express());
@@ -64,7 +64,7 @@ async function main(): Promise {
if (environment !== "development" && config.secret === "CHANGE ME") {
throw new Error(
"Secret not set - this should never be the case outside of " +
- "development. Please set a real random secret value in 'config.json5'."
+ "development. Please set a real random secret value in 'config.json5'.",
);
}
@@ -75,11 +75,19 @@ async function main(): Promise {
app.use(
expressWinston.logger({
winstonInstance: Logging.logger,
- })
+ }),
);
app.get("/api/version", async (req, res) => res.json(config.version));
+ app.get("/api/config", async (req, res) =>
+ res.json({
+ version: config.version,
+ sources: state.sources.clientInfo(),
+ adverts: config.adverts,
+ }),
+ );
+
app.get("/api/games", async (req, res) => {
const result = [];
for await (const summary of state.store.lobbySummaries()) {
@@ -92,14 +100,14 @@ async function main(): Promise {
const { gameCode, token, tasks } = await state.store.newLobby(
CreateLobby.validate(req.body),
config.secret,
- config.defaults
+ config.defaults,
);
for (const task of tasks) {
state.tasks.enqueue(state, task);
}
res.append(
"Location",
- `${req.protocol}://${req.hostname}/${config.basePath}api/games/${gameCode}`
+ `${req.protocol}://${req.hostname}/${config.basePath}api/games/${gameCode}`,
);
res.status(HttpStatus.CREATED).json(token);
});
@@ -111,7 +119,7 @@ async function main(): Promise {
const claims = Token.validate(
current,
await state.store.id(),
- state.config.secret
+ state.config.secret,
);
if (await state.store.exists(claims.gc)) {
result.push(current);
@@ -133,7 +141,7 @@ async function main(): Promise {
}
const newUser = User.create(
registration,
- lobby.config.audienceMode ? "Spectator" : "Player"
+ lobby.config.audienceMode ? "Spectator" : "Player",
);
if (
wu(Object.values(lobby.users)).find((u) => u.name === registration.name)
@@ -148,7 +156,7 @@ async function main(): Promise {
game.playerOrder.push(id);
if (newUser.role === "Player") {
game.players[id] = Player.initial(
- game.decks.responses.draw(game.rules.handSize)
+ game.decks.responses.draw(game.rules.handSize),
);
}
}
@@ -208,7 +216,7 @@ async function main(): Promise {
expressWinston.errorLogger({
winstonInstance: Logging.logger,
msg: "{{err.message}}",
- })
+ }),
);
app.use((error: Error, req: Request, res: Response, next: NextFunction) => {
@@ -253,7 +261,7 @@ async function main(): Promise {
state.tasks
.loadFromStore(state)
.catch((error) =>
- Logging.logException("Error running store tasks:", error)
+ Logging.logException("Error running store tasks:", error),
);
app.listen(config.listenOn, async () => {