feat: add entries page

This commit is contained in:
Ananth Venkatesh 2025-02-15 01:18:58 -05:00
parent 95cd8fa0a8
commit e1a1471058
Signed by: ananthv
GPG key ID: 4BB578E748CFE4FF
3 changed files with 710 additions and 3 deletions

BIN
assets/img/jentry.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 44 KiB

View file

@ -3,6 +3,7 @@
"direct": { "direct": {
"andrewMacmurray/elm-simple-animation": "2.3.2", "andrewMacmurray/elm-simple-animation": "2.3.2",
"avh4/elm-color": "1.0.0", "avh4/elm-color": "1.0.0",
"elm-community/list-extra": "8.7.0",
"elm-explorations/webgl": "1.1.3", "elm-explorations/webgl": "1.1.3",
"elm/browser": "1.0.2", "elm/browser": "1.0.2",
"elm/core": "1.0.5", "elm/core": "1.0.5",

View file

@ -20,6 +20,8 @@ import Html.Attributes
import Http import Http
import Json.Decode as Decode import Json.Decode as Decode
import Length import Length
import List exposing (length)
import List.Extra exposing (getAt)
import Obj.Decode import Obj.Decode
import Pixels import Pixels
import Point3d exposing (Point3d) import Point3d exposing (Point3d)
@ -76,7 +78,20 @@ main =
type alias Model = type alias Model =
{ w : Int, h : Int, last : String, url : Url.Url, key : Nav.Key, mesh : Maybe Object3d, textures : Maybe (Material.Textured Obj.Decode.ObjCoordinates), angle : Float, radius : Float, elevation : Float } { w : Int
, h : Int
, last : String
, url : Url.Url
, key : Nav.Key
, mesh : Maybe Object3d
, textures : Maybe (Material.Textured Obj.Decode.ObjCoordinates)
, angle : Float
, radius : Float
, elevation : Float
, mainColor : Color.Color
, show : Bool
, entry : String
}
type alias Flags = type alias Flags =
@ -89,7 +104,22 @@ init flags url key =
( width, height ) = ( width, height ) =
flags flags
in in
( { w = width, h = height, last = "", url = url, key = key, mesh = Nothing, textures = Nothing, angle = 0, radius = 7.5, elevation = 5 }, Cmd.batch [ getMesh, getTexture ] ) ( { w = width
, h = height
, last = ""
, url = url
, key = key
, mesh = Nothing
, textures = Nothing
, angle = 0
, radius = 7.5
, elevation = 5
, mainColor = Color.rgb 173 111 101
, show = False
, entry = "zero"
}
, Cmd.batch [ getMesh, getTexture ]
)
type alias Object3d = type alias Object3d =
@ -106,6 +136,7 @@ type Msg
| Copy String String | Copy String String
| Key String | Key String
| Scroll String | Scroll String
| ScrollToEntry String
modulo : Float -> Float -> Float modulo : Float -> Float -> Float
@ -118,6 +149,67 @@ canonicalize angle =
modulo 360 angle modulo 360 angle
frac : Float -> Float
frac x =
x - toFloat (floor x)
mainColors : List ( Float, Float, Float )
mainColors =
[ ( 173, 111, 101 ), ( 200, 0, 100 ), ( 100, 0, 200 ), ( 100, 200, 0 ), ( 200, 100, 0 ), ( 173, 111, 101 ) ]
blend : Float -> ( Float, Float, Float ) -> ( Float, Float, Float ) -> Color.Color
blend p ( r1, g1, b1 ) ( r2, g2, b2 ) =
let
blendFlux : Float -> Float -> Float -> Int
blendFlux t a b =
round (sqrt ((1 - t) * (a ^ 2) + t * (b ^ 2)))
in
Color.rgb255 (blendFlux p r1 r2) (blendFlux p g1 g2) (blendFlux p b1 b2)
blender : Float -> Color.Color
blender t =
let
n : Int
n =
length mainColors
p : Float
p =
frac (t * toFloat n)
c1 : Maybe ( Float, Float, Float )
c1 =
getAt (floor (t * toFloat n)) mainColors
c2 : Maybe ( Float, Float, Float )
c2 =
getAt (ceiling (t * toFloat n)) mainColors
in
case c2 of
Nothing ->
case c1 of
Nothing ->
-- no colors to blend
Color.rgb255 173 111 101
Just ( r, g, b ) ->
-- missing color to blend
Color.rgb255 (round r) (round g) (round b)
Just color2 ->
case c1 of
Nothing ->
-- missing main color to blend
Color.rgb255 173 111 101
Just color1 ->
-- blend both colors
blend p color1 color2
update : Msg -> Model -> ( Model, Cmd Msg ) update : Msg -> Model -> ( Model, Cmd Msg )
update msg model = update msg model =
let let
@ -170,6 +262,8 @@ update msg model =
| angle = canonicalize (model.angle + 2 * (2 + sin (toFloat (Time.posixToMillis time) / 1000))) | angle = canonicalize (model.angle + 2 * (2 + sin (toFloat (Time.posixToMillis time) / 1000)))
, radius = 7.5 + 1 / 8 * model.radius + 5 * 13 / 11 * sin (toFloat (Time.posixToMillis time + 250) / 1000) , radius = 7.5 + 1 / 8 * model.radius + 5 * 13 / 11 * sin (toFloat (Time.posixToMillis time + 250) / 1000)
, elevation = 9 + 1 / 4 * model.elevation + 5 * cos (toFloat (Time.posixToMillis time) / 1000) , elevation = 9 + 1 / 4 * model.elevation + 5 * cos (toFloat (Time.posixToMillis time) / 1000)
, mainColor = blender (frac (toFloat (Time.posixToMillis time) / 3500))
, show = modBy 4000 (Time.posixToMillis time) > 2000
} }
Copy label text -> Copy label text ->
@ -181,6 +275,9 @@ update msg model =
Scroll loc -> Scroll loc ->
( model, scrollTo loc ) ( model, scrollTo loc )
ScrollToEntry entry ->
( { model | entry = entry }, scrollTo entry )
keyDecoder : Decode.Decoder String keyDecoder : Decode.Decoder String
keyDecoder = keyDecoder =
@ -234,6 +331,22 @@ linkBtn disp addr =
newTabLink btnStyle { url = addr, label = text (String.toUpper disp) } newTabLink btnStyle { url = addr, label = text (String.toUpper disp) }
linkBtnInt : String -> String -> Element msg
linkBtnInt disp addr =
link btnStyle { url = addr, label = text (String.toUpper disp) }
entryLink : String -> String -> Element Msg
entryLink disp addr =
button
[ Font.underline
, Font.bold
]
{ onPress = Just (ScrollToEntry addr)
, label = text disp
}
btn : String -> Msg -> Element Msg btn : String -> Msg -> Element Msg
btn disp act = btn disp act =
button btnStyle { onPress = Just act, label = text (String.toUpper disp) } button btnStyle { onPress = Just act, label = text (String.toUpper disp) }
@ -370,7 +483,10 @@ itemize multikeys entry =
sitemap : Dict String (Model -> List (Html Msg)) sitemap : Dict String (Model -> List (Html Msg))
sitemap = sitemap =
Dict.fromList (itemize ([ "/", "/index.html" ] ++ getPaths "src" ++ getPaths "index" ++ getPaths "home") pageHome) Dict.fromList
(itemize ([ "/", "/index.html" ] ++ getPaths "src" ++ getPaths "index" ++ getPaths "home") pageHome
++ itemize (getPaths "entries") pageEntries
)
loadUrl : Model -> List (Html Msg) loadUrl : Model -> List (Html Msg)
@ -608,6 +724,407 @@ pageHome model =
] ]
pageEntries : Model -> List (Html Msg)
pageEntries model =
[ Element.layout
[ width fill
]
(column [ width fill ]
[ column
[ width fill
, height fill
, spacing (vh2pt model -100)
, id "zero"
]
[ el
[ width fill
, height (vh2px model 100)
, Background.color black
]
Element.none
, animatedEl crossfadeIn
[ width fill
, height (vh2px model 100)
, Background.gradient { angle = 360 - 45, steps = [ rgb255 100 125 50, rgb255 125 50 100 ] }
]
Element.none
, animatedEl crossfadeOut
[ width fill
, height (vh2px model 100)
, Background.gradient { angle = 360 - 45, steps = [ rgb255 100 50 175, rgb255 100 175 50 ] }
]
Element.none
, el
([ alignLeft
, alignTop
, width (vw2px model 50)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 20 - 96
, bottom = 0
, left = vw2pt model 10
, right = 0
}
]
++ heading
)
(column
[ spacing 35
]
[ menu model
, text "Meet the entries"
, paragraph bodyText
[ entryLink "A entry" "A"
, text ", "
, entryLink "Bentry" "B"
, text " (Office of the President), "
, entryLink "Centry" "C"
, text " (the C is silent), "
, entryLink "Dentry" "D"
, text ", "
, entryLink "E entry" "E"
, text ", "
, entryLink "Fentry" "F"
, text ", "
, entryLink "Gentry" "G"
, text " (hard G), "
, entryLink "Hentry" "H"
, text ", 𝑖 entry (imaginary),"
, image [ height (px 24), padding 10 ] { description = "flag of j entry", src = "../assets/img/jentry.png" }
, entryLink "Jentry" "J"
]
, column [ spacing 15 ]
[ row
[ spacing 15
]
[ linkBtnInt " Back" "/"
, btn "Explore " (ScrollToEntry "J")
]
]
]
)
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "zero" then
view3DColors model
else
Element.none
)
]
, column
(page model ++ [ id "J", Background.color (rgb255 76 76 254) ])
[ column (pageText model)
[ row [ spacing 10 ]
[ btn "" (ScrollToEntry "zero")
, btn "" (ScrollToEntry "A")
]
, paragraph
subheading
[ text "Welcome to the Jentry." ]
, paragraph (bodyText ++ [ Font.size 32 ])
[ text "(J Entry) The heart of MacGregor. Historic culture, big lore. Jentry memes. Greatest MacGregor Housecomm representation of any entry. Jamily is forever."
, image [ width (px 48), padding 12 ] { description = "flag of j entry", src = "../assets/img/jentry.png" }
]
]
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "J" then
view3DEntries "J" model
else
Element.none
)
]
, column
(page model ++ [ id "A", Background.color (rgb255 5 93 243) ])
[ column (pageText model)
[ row [ spacing 10 ]
[ btn "" (ScrollToEntry "J")
, btn "" (ScrollToEntry "B")
]
, paragraph
subheading
[ text "Welcome to A Entry." ]
, paragraph (bodyText ++ [ Font.size 32 ])
[ text "Top floor. Top dog. Lots of dogs, in fact. Historic MacGregor cultural epicenter."
]
]
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "A" then
view3DEntries "A" model
else
Element.none
)
]
, column
(page model ++ [ id "B", Background.color (rgb255 5 180 93) ])
[ column (pageText model)
[ row [ spacing 10 ]
[ btn "" (ScrollToEntry "A")
, btn "" (ScrollToEntry "C")
]
, paragraph
subheading
[ text "Welcome to the Bentry." ]
, paragraph (bodyText ++ [ Font.size 32 ])
[ text "(B Entry) Office of the President. Sweeping views. Crossroads of the High Rise. B is for burgers?"
]
]
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "B" then
view3DEntries "B" model
else
Element.none
)
]
, column
(page model ++ [ id "C", Background.color (rgb255 5 140 40) ])
[ column (pageText model)
[ row [ spacing 10 ]
[ btn "" (ScrollToEntry "B")
, btn "" (ScrollToEntry "D")
]
, paragraph
subheading
[ text "Welcome to Centry." ]
, paragraph (bodyText ++ [ Font.size 32 ])
[ text "(C Entry) Ceriously quiet. High enough to see the clouds, but low enough to stay grounded. MacGregor's creative capital core."
]
]
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "C" then
view3DEntries "C" model
else
Element.none
)
]
, column
(page model ++ [ id "D", Background.color (rgb255 240 140 40) ])
[ column (pageText model)
[ row [ spacing 10 ]
[ btn "" (ScrollToEntry "C")
, btn "" (ScrollToEntry "E")
]
, paragraph
subheading
[ text "Welcome to Dentry." ]
, paragraph (bodyText ++ [ Font.size 32 ])
[ text "(D Entry) Office of the Vice President. Active after dark. Diligently working until dusk. The culture is palpable."
]
]
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "D" then
view3DEntries "D" model
else
Element.none
)
]
, column
(page model ++ [ id "E", Background.color (rgb255 40 20 30) ])
[ column (pageText model)
[ row [ spacing 10 ]
[ btn "" (ScrollToEntry "D")
, btn "" (ScrollToEntry "F")
]
, paragraph
subheading
[ text "Welcome to E entry." ]
, paragraph (bodyText ++ [ Font.size 32 ])
[ text "Board games over Briggs field. Excellence in everything. End of story."
]
]
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "E" then
view3DEntries "E" model
else
Element.none
)
]
, column
(page model ++ [ id "F", Background.color (rgb255 200 50 75) ])
[ column (pageText model)
[ row [ spacing 10 ]
[ btn "" (ScrollToEntry "E")
, btn "" (ScrollToEntry "G")
]
, paragraph
subheading
[ text "Welcome to the Fentry." ]
, paragraph (bodyText ++ [ Font.size 32 ])
[ text "(F entry) F is for Food. F is for Feast. F is for First (low rise block). F is Forever."
]
]
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "F" then
view3DEntries "F" model
else
Element.none
)
]
, column
(page model ++ [ id "G", Background.color (rgb255 200 50 150) ])
[ column (pageText model)
[ row [ spacing 10 ]
[ btn "" (ScrollToEntry "F")
, btn "" (ScrollToEntry "H")
]
, paragraph
subheading
[ text "Welcome to the Gentry." ]
, paragraph (bodyText ++ [ Font.size 32 ])
[ text "(G entry) G is for Greatness. Spans the Charles. Overlooks Fenway. Good things start in Gentry."
]
]
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "G" then
view3DEntries "G" model
else
Element.none
)
]
, column
(page model ++ [ id "H", Background.color (rgb255 200 150 50) ])
[ column (pageText model)
[ row [ spacing 10 ]
[ btn "" (ScrollToEntry "G")
, btn "TOP" (ScrollToEntry "zero")
]
, paragraph
subheading
[ text "Welcome to the Hentry." ]
, paragraph (bodyText ++ [ Font.size 32 ])
[ text "(H entry) Lots of chickens. Crossroads of the Low Rise. Largest entry in MacGregor."
]
]
, el
[ alignRight
, alignTop
, width (vw2px model 60)
, height (vh2px model 100)
, paddingEach
{ top = vh2pt model 25
, bottom = vh2pt model 25
, left = 0
, right = 0
}
]
(if model.entry == "H" then
view3DEntries "H" model
else
Element.none
)
]
]
)
]
notFound : Model -> List (Html Msg) notFound : Model -> List (Html Msg)
notFound model = notFound model =
[ Element.layout [ Element.layout
@ -785,6 +1302,195 @@ view3D model =
) )
view3DColors : Model -> Element msg
view3DColors model =
Element.html
(let
entity : Entity Obj.Decode.ObjCoordinates
entity =
case model.mesh of
Nothing ->
Scene3d.mesh (Material.matte model.mainColor) pyramidMesh
Just mesh ->
Scene3d.mesh (Material.matte model.mainColor) (Mesh.texturedFacets mesh)
camera : Camera3d.Camera3d Length.Meters coordinates
camera =
Camera3d.perspective
{ viewpoint =
Viewpoint3d.lookAt
{ focalPoint = Point3d.origin
, eyePoint =
let
theta : Angle.Angle
theta =
Angle.degrees (5 / 2 * model.angle)
in
Point3d.meters (10 * Angle.cos theta) 2 (10 * Angle.sin theta)
, upDirection = Direction3d.xy (Angle.degrees 90)
}
, verticalFieldOfView = Angle.degrees 100
}
in
Scene3d.sunny
{ entities = [ entity ]
, camera = camera
, upDirection = Direction3d.z
, sunlightDirection = Direction3d.yz (Angle.degrees -120)
, background = Scene3d.transparentBackground
, clipDepth = Length.centimeters 1
, shadows = False
, dimensions = ( Pixels.int (round (vw model 60)), Pixels.int (round (vh model 100)) )
}
)
view3DEntries : String -> Model -> Element msg
view3DEntries entry model =
Element.html
(let
entity : Entity Obj.Decode.ObjCoordinates
entity =
case model.mesh of
Nothing ->
Scene3d.mesh (Material.matte model.mainColor) pyramidMesh
Just mesh ->
case model.textures of
Nothing ->
Scene3d.mesh (Material.matte model.mainColor) (Mesh.texturedFacets mesh)
Just textures ->
if model.show then
Scene3d.mesh (Material.matte model.mainColor) (Mesh.texturedFacets mesh)
else
Scene3d.mesh textures (Mesh.texturedFacets mesh)
height : Float
height =
case entry of
"A" ->
10
"B" ->
7
"C" ->
5
"E" ->
1
_ ->
2
perspective : Angle.Angle
perspective =
let
theta : Float
theta =
180 * frac (model.angle / 180)
winding : Float
winding =
360 * frac (model.angle / 360)
direction : Float
direction =
abs (180 - winding) / (180 - winding)
interp : Float -> Float -> Angle.Angle
interp a b =
Angle.degrees
(if direction > 0 then
a + (b - a) * theta / 180
else
b + (a - b) * theta / 180
)
in
case entry of
"J" ->
interp -45 30
"E" ->
interp 30 110
"F" ->
interp 140 230
"G" ->
interp 230 290
"H" ->
interp 290 315
_ ->
Angle.degrees model.angle
orbit : Float
orbit =
case entry of
"A" ->
4
"B" ->
6
"C" ->
6
"D" ->
10
"F" ->
12
"G" ->
10
"J" ->
12
"H" ->
11
_ ->
7
camera : Camera3d.Camera3d Length.Meters coordinates
camera =
Camera3d.perspective
{ viewpoint =
Viewpoint3d.lookAt
{ focalPoint = Point3d.origin
, eyePoint =
let
theta : Angle.Angle
theta =
perspective
in
Point3d.meters (orbit * Angle.cos theta) height (orbit * Angle.sin theta)
, upDirection = Direction3d.xy (Angle.degrees 90)
}
, verticalFieldOfView = Angle.degrees 60
}
in
Scene3d.sunny
{ entities = [ entity ]
, camera = camera
, upDirection = Direction3d.z
, sunlightDirection = Direction3d.yz (Angle.degrees -120)
, background = Scene3d.transparentBackground
, clipDepth = Length.centimeters 1
, shadows = False
, dimensions = ( Pixels.int (round (vw model 60)), Pixels.int (round (vh model 100)) )
}
)
view3DTower : Model -> Element msg view3DTower : Model -> Element msg
view3DTower model = view3DTower model =
Element.html Element.html