@@ -1,10 +1,7 @@
11module Yoga.SVG.Image where
22
33import Prelude
4−
5−import Color (toHexString)
64import Data.Array (elem, foldl, head, intercalate, snoc, uncons, zip, (:))
7−import Data.Array.NonEmpty as NEA
85import Data.Int (round, toNumber)
96import Data.Maybe (Maybe(..), maybe)
107import Data.String (codePointFromChar)
@@ -12,114 +9,136 @@ import Data.String as String
129import Data.Tuple.Nested ((/\))
1310import Effect (Effect)
1411import Effect.Unsafe (unsafePerformEffect)
15−import JSS (jssClasses)
1612import Random.PseudoRandom (mkSeed, randomRs)
1713import React.Basic (JSX, element)
1814import React.Basic.DOM (unsafeCreateDOMComponent)
1915import React.Basic.DOM as HTML
2016import React.Basic.DOM.SVG as SVG
17+import React.Basic.Emotion as E
2118import React.Basic.Hooks (ReactComponent)
2219import React.Basic.Hooks as React
20+import Yoga.Block.Container.Style (colour)
2321import Yoga.DOM.Hook (useBoundingBox)
2422import Yoga.SVG.Icon (Raw)
2523import Yoga.Scroll.Hook (useScrollYPosition)
26−import Yoga.Theme.Styles (makeStylesJSS, useTheme)
27−import Yoga.Theme.Types (CSSTheme)
2824
2925foreign import landingPageDarkRaw ∷ ∀ r. Raw r
3026
3127foreign import landingPageLightRaw ∷ ∀ r. Raw r
3228
29+animatedCursor =
30+ E.keyframes -- [TODO] how?
31+ { "0%": E.css { opacity: E.str "0.0" }
32+ , "50%": E.css { opacity: E.str "1.0" }
33+ }
34+
35+movingCloud1 =
36+ E.keyframes
37+ { "0%": E.css { transform: E.str "translate3d(-140%, 4%, 0)" }
38+ , "100%": E.css { transform: E.str "translate3d(70%, -4%, 0)" }
39+ }
40+
41+movingCloud2 =
42+ E.keyframes
43+ { "0%": E.css { transform: E.str "translate3d(-10%, 0%, 0)" }
44+ , "100%": E.css { transform: E.str "translate3d(10%, 0%, 0)" }
45+ }
46+
47+twinkle =
48+ E.keyframes
49+ { "0%": E.css { fill: E.str "rgba(250, 250, 250, 0.2)" }
50+ , "50%": E.css { fill: E.str "rgba(250, 250, 250, 0.7)" }
51+ , "70%": E.css { fill: E.str "rgba(250, 250, 250, 0.4)" }
52+ , "100%": E.css { fill: E.str "rgba(250, 250, 250, 1.0)" }
53+ }
54+
55+twinkleFast =
56+ E.keyframes
57+ { "0%": E.css { fill: E.str "rgba(250, 250, 250, 0.7)" }
58+ , "100%": E.css { fill: E.str "rgba(250, 250, 250, 0.78)" }
59+ }
60+
61+rotateStars =
62+ E.keyframes
63+ { "0%": E.css { transform: E.str "rotate(0deg) scale(1,1)" }
64+ , "100%": E.css { transform: E.str "rotate(1deg) scale(1.01, 1.01)" }
65+ }
66+
67+mkClasses theme =
68+ { laptopBackground:
69+ E.css
70+ { fill: E.str colour.background
71+ }
72+ , lightEllipsis:
73+ E.css
74+ { cx: E.str if theme.isLight then "721" else "175"
75+ , cy: E.str if theme.isLight then "355" else "310"
76+ , rx: E.str if theme.isLight then "647" else "330"
77+ , ry: E.str if theme.isLight then "310" else "252"
78+ }
79+ , laptopBody:
80+ E.css
81+ { fill: E.str if theme.isLight then "url(#_Linear9)" else "url(#_Linear12)"
82+ }
83+ , screenText:
84+ E.css
85+ { fontFamily: E.var "--font-mono"
86+ , fontSize: E.str "0.85em"
87+ , fill: E.str colour.text
88+ }
89+ , keyword: E.css { fill: E.str "green" }
90+ , typeName: E.css { fill: E.str "rebeccapurple" }
91+ , cursor:
92+ E.css
93+ { animation: E.str "$animatedCursor 0.5s linear infinite"
94+ , animationDirection: E.str "alternate"
95+ }
96+ , movingCloud1:
97+ E.css
98+ { animation: E.str "$movingCloud1 200s linear infinite"
99+ , animationDelay: E.str "-150s"
100+ }
101+ , movingCloud2:
102+ E.css
103+ { animation: E.str "$movingCloud2 120s linear infinite"
104+ , animationDirection: E.str "alternate"
105+ }
106+ , movingCloud3:
107+ E.css
108+ { animation: E.str "$movingCloud2 120s linear infinite"
109+ , animationDelay: E.str "-120s"
110+ , animationDirection: E.str "alternate"
111+ }
112+ , twinklingStar1:
113+ E.css
114+ { animation: E.str "$twinkleFast 0.2s ease-in-out infinite"
115+ , animationDirection: E.str "alternate"
116+ }
117+ , twinklingStar2:
118+ E.css
119+ { animation: E.str "$twinkle 8s ease-in-out infinite"
120+ , animationDelay: E.str "4s"
121+ , animationDirection: E.str "alternate"
122+ }
123+ , twinklingStar3:
124+ E.css
125+ { animation: E.str "$twinkle 4s ease-in-out infinite"
126+ , animationDelay: E.str "3s"
127+ , animationDirection: E.str "alternate"
128+ }
129+ , stars:
130+ E.css
131+ { animation: E.str "$rotateStars 30s linear infinite"
132+ , transformOrigin: E.str "top center"
133+ , animationDirection: E.str "alternate"
134+ }
135+ }
136+
33137mkLandingPageBackground ∷ Effect (ReactComponent { className ∷ String })
34138mkLandingPageBackground = do
35− useStyles <-
36− makeStylesJSS
37− $ jssClasses \(theme ∷ CSSTheme) ->
38− { laptopBackground:
39− { fill: theme.backgroundColour # toHexString
40− }
41− , lightEllipsis:
42− { cx: if theme.isLight then "721" else "175"
43− , cy: if theme.isLight then "355" else "310"
44− , rx: if theme.isLight then "647" else "330"
45− , ry: if theme.isLight then "310" else "252"
46− }
47− , laptopBody:
48− { fill: if theme.isLight then "url(#_Linear9)" else "url(#_Linear12)"
49− }
50− , screenText:
51− { fontFamily: NEA.head theme.codeFontFamily
52− , fontSize: "0.85em"
53− , fill: theme.textColour # toHexString
54− }
55− , keyword: { fill: theme.green # toHexString }
56− , typeName: { fill: theme.purple # toHexString }
57− , "@keyframes animatedCursor":
58− { "0%": { opacity: "0.0" }
59− , "50%": { opacity: "1.0" }
60− }
61− , cursor:
62− { animation: "$animatedCursor 0.5s linear infinite"
63− , animationDirection: "alternate"
64− }
65− , "@keyframes movingCloud1":
66− { "0%": { transform: "translate3d(-140%, 4%, 0)" }
67− , "100%": { transform: "translate3d(70%, -4%, 0)" }
68− }
69− , movingCloud1:
70− { animation: "$movingCloud1 200s linear infinite"
71− , animationDelay: "-150s"
72− }
73− , "@keyframes movingCloud2":
74− { "0%": { transform: "translate3d(-10%, 0%, 0)" }
75− , "100%": { transform: "translate3d(10%, 0%, 0)" }
76− }
77− , movingCloud2:
78− { animation: "$movingCloud2 120s linear infinite"
79− , animationDirection: "alternate"
80− }
81− , movingCloud3:
82− { animation: "$movingCloud2 120s linear infinite"
83− , animationDelay: "-120s"
84− , animationDirection: "alternate"
85− }
86− , "@keyframes twinkle":
87− { "0%": { fill: "rgba(250, 250, 250, 0.2)" }
88− , "50%": { fill: "rgba(250, 250, 250, 0.7)" }
89− , "70%": { fill: "rgba(250, 250, 250, 0.4)" }
90− , "100%": { fill: "rgba(250, 250, 250, 1.0)" }
91− }
92− , "@keyframes twinkleFast":
93− { "0%": { fill: "rgba(250, 250, 250, 0.7)" }
94− , "100%": { fill: "rgba(250, 250, 250, 0.78)" }
95− }
96− , twinklingStar1:
97− { animation: "$twinkleFast 0.2s ease-in-out infinite"
98− , animationDirection: "alternate"
99− }
100− , twinklingStar2:
101− { animation: "$twinkle 8s ease-in-out infinite"
102− , animationDelay: "4s"
103− , animationDirection: "alternate"
104− }
105− , twinklingStar3:
106− { animation: "$twinkle 4s ease-in-out infinite"
107− , animationDelay: "3s"
108− , animationDirection: "alternate"
109− }
110− , "@keyframes rotateStars":
111− { "0%": { transform: "rotate(0deg) scale(1,1)" }
112− , "100%": { transform: "rotate(1deg) scale(1.01, 1.01)" }
113− }
114− , stars:
115− { animation: "$rotateStars 30s linear infinite"
116− , transformOrigin: "top center"
117− , animationDirection: "alternate"
118− }
119− }
120139 React.reactComponent "LandingPageBackground" \{ className } -> React.do
121− theme <- useTheme
122− classes <- useStyles {}
140+ let theme = { isLight: true } -- [TODO]
141+ let classes = mkClasses theme
123142 scrollY <- useScrollYPosition
124143 bb /\ ref <- useBoundingBox
125144 let
@@ -157,37 +176,42 @@ mkLandingPageBackground = do
157176 { d: "M800.828 448.051V-3.513H-3.028v451.564h803.856z"
158177 , fill: "url(#_Linear2)"
159178 }
160− , SVG.ellipse
161− { className: classes.lightEllipsis
179+ , E.element SVG.ellipse'
180+ { className: "light-ellipsis"
181+ , css: classes.lightEllipsis
162182 , fill: "url(#_Radial3)"
163183 }
164184 , stars classes.stars classes
165185 -- bgcloud
166− , SVG.path
186+ , E.element SVG.path'
167187 { d: "M610.291 177.316c-35.879-.428-84.67-.691-138.392-.689-38.684.002-74.811.141-105.402.38-49.14-2.745-80.877-9.954-80.877-14.718 0-1.333 2.655.335 7.294-.896 96.772-.175 171.302-1.213 171.302-2.464 0-1.107-59.462-3.372-140.106-3.687 38.487-2.059 102.543-4.972 173.823-4.972 11.996 0 23.761.07 35.216.206 20.76-4.638 60.945-2.784 107.07-2.784 36.08 0 68.525-.075 90.882 2.983 32.491.899 72.142-1.186 114.922-.379 38.68.729 74.805 1.273 105.395 1.613 49.101 3.673 80.599 8.516 80.54 13.279-.016 1.334-2.504 2.58-7.158 3.723-96.763-1.654-171.297-2.024-171.312-.774-.013 1.108 58.473 3.15 139.105 4.989-38.514 3.085-101.653 4.38-172.925 3.033-11.995-.227-23.757-.52-35.209-.872-20.815 4.245-61.034 6.631-107.154 5.759-24.754-.468-47.786-1.809-67.014-3.73z"
168188 , fill: if theme.isLight then "#ddf0fb" else "#240c3c"
169− , className: classes.movingCloud1
189+ , css: classes.movingCloud1
190+ , className: "moving-cloud-1"
170191 , transform: "translate(0 " <> show (scrolled * 20.0) <> ")"
171192 }
172193 -- bgcloud
173− , SVG.path
194+ , E.element SVG.path'
174195 { d: "M609.299 171.607c-35.88-.338-84.67-.545-138.392-.543-38.684.001-74.812.111-105.402.299-49.141-2.166-80.694-5.519-80.694-9.279 0-1.052 2.472-2.073 7.111-3.045 96.772-.137 171.302-.957 171.302-1.944-.001-.874-58.506-1.614-139.15-1.862 38.471-3.009 101.587-4.973 172.867-4.973 11.996 0 23.761.055 35.216.162 20.76-3.66 60.945-6.143 107.07-6.143 36.08 0 68.525 1.519 90.882 3.933 32.491.709 72.142 1.432 114.922 2.069 38.68.575 74.805 1.005 105.395 1.272 49.101 2.9 80.598 6.722 80.54 10.482-.017 1.052-2.505 2.036-7.158 2.938-96.763-1.305-171.297-1.597-171.312-.611-.014.875 58.473 2.486 139.104 3.938-38.514 2.435-101.653 3.457-172.924 2.394a3771.84 3771.84 0 01-35.21-.688c-20.814 3.35-61.033 5.234-107.153 4.545-24.754-.369-47.786-1.428-67.014-2.944z"
175196 , fill: if theme.isLight then "#d8effd" else "#120b38"
176− , className: classes.movingCloud1
197+ , css: classes.movingCloud1
198+ , className: "moving-cloud-1"
177199 , transform: "translate(0 " <> show (scrolled * 20.0) <> ")"
178200 }
179201 -- rightcloud
180− , SVG.path
202+ , E.element SVG.path'
181203 { d: "M684.234 222.269c-12.108-1.642-29.721-2.832-29.76-5.218-.063-3.861 34.226-4.225 76.6-3.504 42.375.721 75.9-2.707 76.956 1.008 2.476 8.704-12.549 8.552-32.56 10.141-12.403-.58-28.24.216-44.434.479-16.193.263-35.197-1.331-46.802-2.906z"
182204 , fill: if theme.isLight then "#d8effd" else "url(#_Linear4)"
183− , className: classes.movingCloud2
205+ , css: classes.movingCloud2
206+ , className: "moving-cloud-2"
184207 , transform: "translate(0 " <> show (scrolled * 20.0) <> ")"
185208 }
186209 -- leftcloud
187− , SVG.path
210+ , E.element SVG.path'
188211 { d: "M130.064 213.821c42.381 0 76.789.237 76.789 4.224 0 3.986-14.408 4.223-56.789 4.223-42.38 0-96.788.208-96.788-4.223s34.408-4.224 76.788-4.224z"
189212 , fill: if theme.isLight then "#d8effd" else "#a7847d"
190− , className: classes.movingCloud3
213+ , css: classes.movingCloud3
214+ , className: "moving-cloud-3"
191215 , transform: "translate(0 " <> show (scrolled * 20.0) <> ")"
192216 }
193217 , mountains theme.isLight scrolled
@@ -195,7 +219,7 @@ mkLandingPageBackground = do
195219 , buildings theme.isLight scrolled
196220 -- bar
197221 , SVG.path
198− { fill: theme.backgroundColour # toHexString
222+ { fill: colour.background
199223 , d: "M-3.028 431.298h826.844v20.006H-3.028z"
200224 }
201225 -- laptop
@@ -208,16 +232,16 @@ mkLandingPageBackground = do
208232
209233laptop ∷
210234 ∀ r.
211− { cursor ∷ String
212− , keyword ∷ String
213− , laptopBackground ∷ String
214− , laptopBody ∷ String
215− , screenText ∷ String
216− , typeName ∷ String
235+ { cursor ∷ E.Style
236+ , keyword ∷ E.Style
237+ , laptopBackground ∷ E.Style
238+ , laptopBody ∷ E.Style
239+ , screenText ∷ E.Style
240+ , typeName ∷ E.Style
217241 | r
218242 } ->
219243 Number -> JSX
220−laptop classes scrolled =
244+laptop styles scrolled =
221245 SVG.g
222246 { children:
223247 [ SVG.path
@@ -228,14 +252,16 @@ laptop classes scrolled =
228252 { d: "M606.064 273.603c0-6.261-5.076-11.337-11.337-11.337H399.673c-6.261 0-11.337 5.076-11.337 11.337v151.521a5.001 5.001 0 005 5h207.728a5.004 5.004 0 003.536-1.464 5.004 5.004 0 001.464-3.536V273.603z"
229253 , fill: "#657390"
230254 }
231− , SVG.path
255+ , E.element SVG.path'
232256 { d: "M599.193 275.905a5.528 5.528 0 00-5.526-5.526H400.604a5.528 5.528 0 00-5.526 5.526v141.039a5.528 5.528 0 005.526 5.526h193.063a5.528 5.528 0 005.526-5.526V275.905z"
233257 , fill: "#130c2b"
234− , className: classes.laptopBackground
258+ , className: "laptop-background"
259+ , css: styles.laptopBackground
235260 }
236− , SVG.path
261+ , E.element SVG.path'
237262 { d: "M655.04 427.228a.458.458 0 00-.457-.457H340.83v4.975a5 5 0 005 5h304.21a5 5 0 005-5v-4.518z"
238− , className: classes.laptopBody
263+ , className: "laptop-body"
264+ , css: styles.laptopBody
239265 }
240266 , SVG.circle
241267 { cx: "497.178"
@@ -256,12 +282,13 @@ laptop classes scrolled =
256282 , fill: "#102708"
257283 , fillOpacity: ".45"
258284 }
259− , SVG.text
285+ , E.element SVG.text'
260286 { x: "407"
261287 , y: "290"
262− , className: classes.screenText
288+ , css: styles.screenText
289+ , className: "screen-text"
263290 , children:
264− textToTspans classes scrolled 407.0
291+ textToTspans styles scrolled 407.0
265292 $ intercalate "\n"
266293 [ "hoistJoker "
267294 , "\t∷ ∀ f g a b"
@@ -374,13 +401,13 @@ mountains isLight scrolled =
374401
375402textToTspans ∷
376403 ∀ r.
377− { cursor ∷ String
378− , keyword ∷ String
379− , typeName ∷ String
404+ { cursor ∷ E.Style
405+ , keyword ∷ E.Style
406+ , typeName ∷ E.Style
380407 | r
381408 } ->
382409 Number -> Number -> String -> Array JSX
383−textToTspans classes scrolled x str = (foldl toTspan { distance: 0.0, acc: [] } lines).acc <> [ cursor ]
410+textToTspans styles scrolled x str = (foldl toTspan { distance: 0.0, acc: [] } lines).acc <> [ cursor ]
384411 where
385412 toTspan { distance, acc } line = case uncons (words line) of
386413 Nothing -> { distance: distance + 1.2, acc }
@@ -389,7 +416,6 @@ textToTspans classes scrolled x str = (foldl toTspan { distance: 0.0, acc: [] }
389416 let
390417 newElems ∷ Array JSX
391418 newElems = _.spans $ foldl mkNewElems { prefix: " ", spans: [] } tail
392−
393419 mkNewElems { prefix, spans } = case _ of
394420 "" -> { prefix: prefix <> " ", spans }
395421 word ->
@@ -397,40 +423,40 @@ textToTspans classes scrolled x str = (foldl toTspan { distance: 0.0, acc: [] }
397423 , spans:
398424 spans
399425 `snoc`
400− SVG.tspan
426+ E.element SVG.tspan'
401427 { children: [ HTML.text $ prefix <> word ]
402− , className: classNameForWord word
428+ , className: "word"
429+ , css: styleForWord word
403430 }
404431 }
405−
406432 first =
407433 SVG.tspan
408434 { x: show x
409435 , dy: show distance <> "em"
410436 , children: [ HTML.text head ]
411437 }
412−
413438 pimped ∷ Array JSX
414439 pimped = first : newElems
415440 { distance: 1.2, acc: acc <> pimped }
416− classNameForWord = case _ of
441+
442+ styleForWord = case _ of
417443 keyword
418− | elem keyword keywords -> classes.keyword
444+ | elem keyword keywords -> styles.keyword
419445 typeName
420− | isTypeName typeName -> classes.typeName
421− other -> ""
446+ | isTypeName typeName -> styles.typeName
447+ other -> mempty
448+
422449 isTypeName s =
423450 let
424451 firstLetter = String.take 1 s
425−
426452 inRange c1 c2 = between (codePointFromChar c1) (codePointFromChar c2)
427−
428453 isLetter =
429454 String.toCodePointArray
430455 >>> head
431456 >>> maybe false (inRange 'a' 'z' || inRange 'A' 'Z')
432457 in
433458 String.toUpper firstLetter == firstLetter && isLetter firstLetter
459+
434460 keywords =
435461 [ "module"
436462 , "∷"
@@ -454,21 +480,28 @@ textToTspans classes scrolled x str = (foldl toTspan { distance: 0.0, acc: [] }
454480 , "else"
455481 , "import"
456482 ]
483+
457484 words line = split
458485 where
459486 split = String.split (String.Pattern " ") line
487+
460488 lines = String.split (String.Pattern "\n") scrolledString
489+
461490 scrolledString = String.take charactersToTake str
462491 where
463492 doneAfterXPercent = 0.5 -- [WARN] Don't set to 0
493+
464494 charactersToTake = round $ scrolled * (1.0 / doneAfterXPercent) * len
495+
465496 len = toNumber $ String.length str
497+
466498 cursor =
467− SVG.tspan
499+ E.element SVG.tspan'
468500 { dx: "0"
469501 , dy: "0"
470502 , children: [ HTML.text "█" ]
471− , className: classes.cursor
503+ , css: styles.cursor
504+ , className: "landing-page-cursor"
472505 , id: "landing-page-cursor"
473506 }
474507
@@ -894,43 +927,38 @@ darkDefs =
894927
895928infixr 8 zip as <%>
896929
897−stars ∷ ∀ css. String -> { twinklingStar1 ∷ String, twinklingStar2 ∷ String, twinklingStar3 ∷ String | css } -> JSX
898−stars className classes =
930+stars ∷ ∀ css. E.Style -> { twinklingStar1 ∷ E.Style, twinklingStar2 ∷ E.Style, twinklingStar3 ∷ E.Style | css } -> JSX
931+stars starStyle allStyles =
899932 let
900933 n = 30
901−
902934 xs = randomRs 0.0 800.0 n $ mkSeed 84
903−
904935 ys = randomRs (-20.0) 200.0 n $ mkSeed 4398
905−
906936 radiuses = randomRs 0.1 1.3 n (mkSeed 15)
907−
908937 opacities = randomRs 0.5 0.9 n (mkSeed 92)
909−
910− classNames =
938+ styles =
911939 randomRs 1 50 n (mkSeed 3)
912940 <#> case _ of
913− 1 -> classes.twinklingStar1
914− 2 -> classes.twinklingStar2
915− 3 -> classes.twinklingStar2
916− _ -> ""
917−
918− zipped = xs <%> ys <%> radiuses <%> classNames <%> opacities
919−
941+ 1 -> allStyles.twinklingStar1
942+ 2 -> allStyles.twinklingStar2
943+ 3 -> allStyles.twinklingStar2
944+ _ -> mempty
945+ zipped = xs <%> ys <%> radiuses <%> styles <%> opacities
920946 children = do
921947 cx /\ cy /\ r /\ className' /\ opacity <- zipped
922948 pure
923− $ SVG.circle
949+ $ E.element SVG.circle'
924950 { cx: show cx
925951 , cy: show cy
926952 , r: show r
927953 , fill: "#eeefff"
928− , className: className'
954+ , css: className'
955+ , className: "starry" -- [TODO]
929956 , opacity: show (opacity - cy / 200.0)
930957 }
931958 in
932− SVG.g
959+ E.element SVG.g'
933960 { fillOpacity: ".67"
934961 , children
935− , className
962+ , css: starStyle
963+ , className: "star-g"
936964 }