4import CSS.Safer (cssSafer)
5import DOM.Hook (useBoundingBox)
6import Data.Array (elem, foldl, head, intercalate, snoc, uncons, zip, (:))
7import Data.Int (round, toNumber)
8import Data.Maybe (Maybe(..), maybe)
9import Data.String (codePointFromChar)
10import Data.String as String
11import Data.Tuple.Nested ((/\))
13import Random.PseudoRandom (mkSeed, randomRs)
14import React.Basic (JSX, element)
15import React.Basic.DOM (unsafeCreateDOMComponent)
16import React.Basic.DOM as HTML
17import React.Basic.DOM.SVG as SVG
18import React.Basic.Hooks (ReactComponent)
19import React.Basic.Hooks as React
21import Scroll.Hook (useScrollYPosition)
22import Theme.Styles (makeStyles, useTheme)
23import Theme.Types (CSSTheme)
25foreign import landingPageDarkRaw ∷ ∀ r. Raw r
27foreign import landingPageLightRaw ∷ ∀ r. Raw r
29mkLandingPageBackground ∷ Effect (ReactComponent { className ∷ String })
30mkLandingPageBackground = do
32 makeStyles \(theme ∷ CSSTheme) ->
35 { fill: theme.backgroundColour
39 { cx: if theme.isLight then "721" else "175"
40 , cy: if theme.isLight then "355" else "310"
41 , rx: if theme.isLight then "647" else "330"
42 , ry: if theme.isLight then "310" else "252"
46 { fill: if theme.isLight then "url(#_Linear9)" else "url(#_Linear12)"
50 { fontFamily: "PragmataPro"
52 , fill: theme.textColour
54 , keyword: cssSafer { fill: theme.green }
55 , typeName: cssSafer { fill: theme.pink }
56 , "@keyframes animatedCursor":
58 { "0%": { opacity: "0.0" }
59 , "50%": { opacity: "1.0" }
63 { animation: "$animatedCursor 0.5s linear infinite"
64 , animationDirection: "alternate"
66 , "@keyframes movingCloud1":
68 { "0%": { transform: "translate3d(-140%, 4%, 0)" }
69 , "100%": { transform: "translate3d(70%, -4%, 0)" }
73 { animation: "$movingCloud1 200s linear infinite"
74 , animationDelay: "-150s"
76 , "@keyframes movingCloud2":
78 { "0%": { transform: "translate3d(-10%, 0%, 0)" }
79 , "100%": { transform: "translate3d(10%, 0%, 0)" }
83 { animation: "$movingCloud2 120s linear infinite"
84 , animationDirection: "alternate"
88 { animation: "$movingCloud2 120s linear infinite"
89 , animationDelay: "-120s"
90 , animationDirection: "alternate"
92 , "@keyframes twinkle":
94 { "0%": { fill: "rgba(250, 250, 250, 0.2)" }
95 , "50%": { fill: "rgba(250, 250, 250, 0.7)" }
96 , "70%": { fill: "rgba(250, 250, 250, 0.4)" }
97 , "100%": { fill: "rgba(250, 250, 250, 1.0)" }
99 , "@keyframes twinkleFast":
101 { "0%": { fill: "rgba(250, 250, 250, 0.7)" }
102 , "100%": { fill: "rgba(250, 250, 250, 0.78)" }
106 { animation: "$twinkleFast 0.2s ease-in-out infinite"
107 , animationDirection: "alternate"
111 { animation: "$twinkle 8s ease-in-out infinite"
112 , animationDelay: "4s"
113 , animationDirection: "alternate"
117 { animation: "$twinkle 4s ease-in-out infinite"
118 , animationDelay: "3s"
119 , animationDirection: "alternate"
121 , "@keyframes rotateStars":
123 { "0%": { transform: "rotate(0deg) scale(1,1)" }
124 , "100%": { transform: "rotate(1deg) scale(1.01, 1.01)" }
128 { animation: "$rotateStars 30s linear infinite"
129 , transformOrigin: "top center"
130 , animationDirection: "alternate"
133 React.component "LandingPageBackground" \{ className } -> React.do
136 scrollY <- useScrollYPosition
137 bb /\ ref <- useBoundingBox
139 scrolled = case bb of
141 Just vh -> scrollY / vh.height
143 $ element (unsafeCreateDOMComponent "svg") -- [TODO] make PR for react-basic to allow for `ref` on svg
145 , viewBox: "0 0 800 447"
146 , preserveAspectRatio: "xMidYMax slice"
147 , xmlns: "http://www.w3.org/2000/svg"
148 , fillRule: "evenodd"
149 , clipRule: "evenodd"
150 , strokeLinejoin: "round"
151 , strokeMiterlimit: "2"
156 , d: "M0 0h800v447H0z"
162 { d: "M0 0h800v447H0z"
167 { clipPath: "url(#a)"
170 { d: "M800.828 448.051V-3.513H-3.028v451.564h803.856z"
171 , fill: "url(#_Linear2)"
174 { className: classes.lightEllipsis
175 , fill: "url(#_Radial3)"
177 , stars classes.stars classes
180 { 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"
181 , fill: if theme.isLight then "#ddf0fb" else "#240c3c"
182 , className: classes.movingCloud1
183 , transform: "translate(0 " <> show (scrolled * 20.0) <> ")"
187 { 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"
188 , fill: if theme.isLight then "#d8effd" else "#120b38"
189 , className: classes.movingCloud1
190 , transform: "translate(0 " <> show (scrolled * 20.0) <> ")"
194 { 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"
195 , fill: if theme.isLight then "#d8effd" else "url(#_Linear4)"
196 , className: classes.movingCloud2
197 , transform: "translate(0 " <> show (scrolled * 20.0) <> ")"
201 { 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"
202 , fill: if theme.isLight then "#d8effd" else "#a7847d"
203 , className: classes.movingCloud3
204 , transform: "translate(0 " <> show (scrolled * 20.0) <> ")"
206 , mountains theme.isLight scrolled
208 , buildings theme.isLight scrolled
211 { fill: theme.backgroundColour
212 , d: "M-3.028 431.298h826.844v20.006H-3.028z"
215 , laptop classes scrolled
226 , laptopBackground ∷ String
227 , laptopBody ∷ String
228 , screenText ∷ String
233laptop classes scrolled =
237 { 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"
241 { 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"
245 { 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"
247 , className: classes.laptopBackground
250 { d: "M655.04 427.228a.458.458 0 00-.457-.457H340.83v4.975a5 5 0 005 5h304.21a5 5 0 005-5v-4.518z"
251 , className: classes.laptopBody
275 , className: classes.screenText
277 textToTspans classes scrolled 407.0
284 , "hoistJoker f (Joker a) ="
291buildings ∷ Boolean -> Number -> JSX
292buildings isLight scrolled =
294 { transform: "translate(0 " <> show (scrolled * 30.0) <> ")"
297 { d: "M195.171 451.298V159.153H92.709v292.145h102.462z"
298 , fill: if isLight then "url(#_Linear4)" else "url(#_Linear5)"
301 { d: "M289.755 451.298V232.811h-57.232v218.487h57.232z"
302 , fill: if isLight then "url(#_Linear5)" else "url(#_Linear6)"
304 , SVG.path -- 4 shade
305 { d: "M207.557 451.298V232.811h25.314v218.487h-25.314zM92.709 451.298V159.153h25.314v292.145H92.709z"
306 , fill: if isLight then "#b4dbfb" else "#130425"
308 , SVG.path -- 5 shade
309 { d: "M323.451 451.298V327.713h-71.555v123.585h71.555z"
310 , fill: if isLight then "url(#_Linear6)" else "url(#_Linear7)"
311 , transform: "translate(0 " <> show (scrolled * 60.0) <> ")"
314 { d: "M323.451 451.298V327.713h-16.692v123.585h16.692z"
315 , fill: if isLight then "url(#_Linear7)" else "url(#_Linear8)"
316 , transform: "translate(0 " <> show (scrolled * 60.0) <> ")"
319 { d: "M272.498 451.298v-95.771H135.943v95.771h136.555z"
320 , fill: if isLight then "url(#_Linear8)" else "url(#_Linear9)"
321 , transform: "translate(0 " <> show (scrolled * 100.0) <> ")"
323 , SVG.path -- 3 shade
324 { d: "M173.526 451.298v-95.771h-37.583v95.771h37.583z"
325 , fill: if isLight then "#b4dbfb" else "#2e1232"
326 , transform: "translate(0 " <> show (scrolled * 100.0) <> ")"
329 { d: "M85.122 451.298V272.442h-97.675v178.856h97.675z"
330 , fill: if isLight then "#b4dbfb" else "url(#_Linear10)"
331 , transform: "translate(0 " <> show (scrolled * 100.0) <> ")"
333 , SVG.path -- 1 shade
334 { d: "M66.649 456.944V272.442H-3.028v184.502h69.677z"
335 , fill: if isLight then "#b4dbfb" else "url(#_Linear11)"
336 , transform: "translate(0 " <> show (scrolled * 100.0) <> ")"
341mountains ∷ Boolean -> Number -> JSX
342mountains isLight scrolled =
344 { transform: "translate(0 " <> show (scrolled * 70.0) <> ")"
347 { d: "M476.098 218.299l81.644 216.765H352.334l123.764-216.765z"
348 , fill: if isLight then "#4560a0" else "#04102d"
351 { d: "M476.098 218.299l21.823 203.477 81.177-8.706-103-194.771z"
352 , fill: if isLight then "#5e84ba" else "#250849"
356 { d: "M369.627 308.648l88.326 125.788H252.546l117.081-125.788z"
357 , fill: if isLight then "#4560a0" else "#04102d"
361 { d: "M326.11 338.332l68.193 96.732H188.896l137.214-96.732zM508.168 243.792l163.526 204.747H415.051l93.117-204.747z"
362 , fill: if isLight then "#4560a0" else "#04102d"
364 , SVG.path -- 1 and 2 light (right)
365 { d: "M508.168 245.355l63.395 75.115-54.953 46.379-8.442-121.494zM369.612 308.648l54.182 77.687-58.981 41.135 4.799-118.822zM326.184 338.326l16.813 23.99-16.887 11.835.074-35.825z"
366 , fill: if isLight then "#5e84ba" else "#250849"
369 , SVG.path -- rightmost dark (left)
370 { d: "M633.647 230.42L809.34 435.064H457.953L633.647 230.42z"
371 , fill: if isLight then "#4560a0" else "#04102d"
373 , SVG.path -- rightmost light (right)
374 { d: "M633.647 230.42L735.22 475.549l86.041-35.653L633.647 230.42z"
375 , fill: if isLight then "#5e84ba" else "#250849"
377 , SVG.path -- peak left
378 { d: "M643.625 254.384c-.571.219-.745.283-1.863.82-2.864 1.395-6.079 3.209-9.184 2.64-6.187-1.134-10.854-6.283-16.725-6.738l17.786-20.729 9.986 24.007z"
379 , fill: if isLight then "#d9d9d9" else "#655578"
381 , SVG.path -- peak right
382 { d: "M654.229 253.4c-.494-.028-.875-.013-.875-.013-3.255-.486-6.617-.192-9.677.982l-.052.02-9.978-23.97 20.582 22.981z"
383 , fill: if isLight then "#fff" else "#9d779e"
395 Number -> Number -> String -> Array JSX
396textToTspans classes scrolled x str = (foldl toTspan { distance: 0.0, acc: [] } lines).acc <> [ cursor ]
398 toTspan { distance, acc } line = case uncons (words line) of
399 Nothing -> { distance: distance + 1.2, acc }
400 Just { head: "", tail: [] } -> { distance: distance + 1.2, acc }
401 Just { head, tail } -> do
404 newElems = _.spans $ foldl mkNewElems { prefix: " ", spans: [] } tail
406 mkNewElems { prefix, spans } = case _ of
407 "" -> { prefix: prefix <> " ", spans }
414 { children: [ HTML.text $ prefix <> word ]
415 , className: classNameForWord word
422 , dy: show distance <> "em"
423 , children: [ HTML.text head ]
427 pimped = first : newElems
428 { distance: 1.2, acc: acc <> pimped }
430 classNameForWord = case _ of
432 | elem keyword keywords -> classes.keyword
434 | isTypeName typeName -> classes.typeName
439 firstLetter = String.take 1 s
441 inRange c1 c2 = between (codePointFromChar c1) (codePointFromChar c2)
444 String.toCodePointArray
446 >>> maybe false (inRange 'a' 'z' || inRange 'A' 'Z')
448 String.toUpper firstLetter == firstLetter && isLetter firstLetter
476 split = String.split (String.Pattern " ") line
478 lines = String.split (String.Pattern "\n") scrolledString
480 scrolledString = String.take charactersToTake str
482 doneAfterXPercent = 0.5 -- [WARN] Don't set to 0
484 charactersToTake = round $ scrolled * (1.0 / doneAfterXPercent) * len
486 len = toNumber $ String.length str
492 , children: [ HTML.text "█" ]
493 , className: classes.cursor
494 , id: "landing-page-cursor"
498defs = if _ then lightDefs else darkDefs
510 , gradientUnits: "userSpaceOnUse"
511 , gradientTransform: "matrix(0 451.564 -803.856 0 398.9 -3.513)"
515 , stopColor: "#68adfc"
519 , stopColor: "#cbe0f8"
523 , stopColor: "#d2cbf8"
533 , gradientUnits: "userSpaceOnUse"
534 , gradientTransform: "scale(293.8304) rotate(83.86 -.11 .484)"
538 , stopColor: "#bde8f7"
542 , stopColor: "#86b6fe"
552 , gradientUnits: "userSpaceOnUse"
553 , gradientTransform: "scale(219.3256) rotate(84.988 -.028 1.133)"
557 , stopColor: "#bde8f7"
561 , stopColor: "#86b6fe"
571 , gradientUnits: "userSpaceOnUse"
572 , gradientTransform: "rotate(90 -6.304 321.409) scale(123.585)"
576 , stopColor: "#bde8f7"
580 , stopColor: "#86b6fe"
590 , gradientUnits: "userSpaceOnUse"
591 , gradientTransform: "scale(132.4342) rotate(68.9 -1.112 2.415)"
595 , stopColor: "#bde8f7"
599 , stopColor: "#86b6fe"
609 , gradientUnits: "userSpaceOnUse"
610 , gradientTransform: "rotate(90 -88.788 166.635) scale(190.951)"
614 , stopColor: "#bde8f7"
618 , stopColor: "#86b6fe"
628 , gradientUnits: "userSpaceOnUse"
629 , gradientTransform: "matrix(314.21 0 0 9.97478 340.83 431.758)"
633 , stopColor: "#a9abc1"
637 , stopColor: "#65687d"
641 , stopColor: "#c3c5d2"
645 , stopColor: "#64677c"
649 , stopColor: "#a4a7c2"
658 , gradientUnits: "userSpaceOnUse"
659 , gradientTransform: "matrix(647.316 0 0 309.935 720.558 355.142)"
663 , stopColor: "#cfcfcf"
668 , stopColor: "#fff552"
686 , gradientUnits: "userSpaceOnUse"
687 , gradientTransform: "matrix(0 451.564 -803.856 0 398.9 -3.513)"
691 , stopColor: "#000434"
695 , stopColor: "#171043"
699 , stopColor: "#573774"
703 , stopColor: "#b8734e"
713 , gradientUnits: "userSpaceOnUse"
714 , gradientTransform: "matrix(-.53832 -17.3125 1.44013 -.04478 749.812 228.864)"
718 , stopColor: "#e59f77"
722 , stopColor: "#a46075"
726 , stopColor: "#914d74"
736 , gradientUnits: "userSpaceOnUse"
737 , gradientTransform: "scale(293.8304) rotate(83.86 -.11 .484)"
741 , stopColor: "#08001b"
745 , stopColor: "#571547"
755 , gradientUnits: "userSpaceOnUse"
756 , gradientTransform: "scale(219.3256) rotate(84.988 -.028 1.133)"
760 , stopColor: "#000418"
764 , stopColor: "#571548"
774 , gradientUnits: "userSpaceOnUse"
775 , gradientTransform: "matrix(46.7869 123.585 -71.5547 27.0893 259.972 327.713)"
779 , stopColor: "#000418"
783 , stopColor: "#571548"
793 , gradientUnits: "userSpaceOnUse"
794 , gradientTransform: "rotate(90 -6.304 321.409) scale(123.585)"
798 , stopColor: "#3a0843"
802 , stopColor: "#9f5c6a"
812 , gradientUnits: "userSpaceOnUse"
813 , gradientTransform: "scale(101.643) rotate(28.107 -6.187 4.945)"
817 , stopColor: "#2c1d2f"
821 , stopColor: "#480445"
831 , gradientUnits: "userSpaceOnUse"
832 , gradientTransform: "scale(-106.68957) rotate(-86.136 -1.645 -.807)"
836 , stopColor: "#2c1d2f"
840 , stopColor: "#480445"
850 , gradientUnits: "userSpaceOnUse"
851 , gradientTransform: "matrix(20.5602 0 -184.503 0 11.25 272.442)"
855 , stopColor: "#000624"
859 , stopColor: "#4f1557"
869 , gradientUnits: "userSpaceOnUse"
870 , gradientTransform: "matrix(314.21 0 0 9.97478 340.83 431.758)"
874 , stopColor: "#a9abc1"
878 , stopColor: "#65687d"
882 , stopColor: "#c3c5d2"
886 , stopColor: "#64677c"
890 , stopColor: "#a4a7c2"
899 , gradientUnits: "userSpaceOnUse"
900 , gradientTransform: "matrix(330.479 0 0 252.189 174.599 310.24)"
909 , stopColor: "#fff552"
919stars ∷ ∀ css. String -> { twinklingStar1 ∷ String, twinklingStar2 ∷ String, twinklingStar3 ∷ String | css } -> JSX
920stars className classes =
924 xs = randomRs 0.0 800.0 n $ mkSeed 84
926 ys = randomRs (-20.0) 200.0 n $ mkSeed 4398
928 radiuses = randomRs 0.1 1.3 n (mkSeed 15)
930 opacities = randomRs 0.5 0.9 n (mkSeed 92)
933 randomRs 1 50 n (mkSeed 3)
935 1 -> classes.twinklingStar1
936 2 -> classes.twinklingStar2
937 3 -> classes.twinklingStar2
940 zipped = xs <%> ys <%> radiuses <%> classNames <%> opacities
943 cx /\ cy /\ r /\ className' /\ opacity <- zipped
950 , className: className'
951 , opacity: show (opacity - cy / 200.0)
keysesc to close
- j k
- move down, up
- enter
- open
- esc
- back, or up a level
- 1 2 3 4
- readme, files, commits, refs
- /
- filter
- y
- copy the clone command
- c
- compare the chosen commit with another (or shift-click both)
- ?
- show or hide this list