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": {
"andrewMacmurray/elm-simple-animation": "2.3.2",
"avh4/elm-color": "1.0.0",
"elm-community/list-extra": "8.7.0",
"elm-explorations/webgl": "1.1.3",
"elm/browser": "1.0.2",
"elm/core": "1.0.5",

View file

@ -20,6 +20,8 @@ import Html.Attributes
import Http
import Json.Decode as Decode
import Length
import List exposing (length)
import List.Extra exposing (getAt)
import Obj.Decode
import Pixels
import Point3d exposing (Point3d)
@ -76,7 +78,20 @@ main =
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 =
@ -89,7 +104,22 @@ init flags url key =
( width, height ) =
flags
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 =
@ -106,6 +136,7 @@ type Msg
| Copy String String
| Key String
| Scroll String
| ScrollToEntry String
modulo : Float -> Float -> Float
@ -118,6 +149,67 @@ canonicalize 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 =
let
@ -170,6 +262,8 @@ update msg model =
| 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)
, 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 ->
@ -181,6 +275,9 @@ update msg model =
Scroll loc ->
( model, scrollTo loc )
ScrollToEntry entry ->
( { model | entry = entry }, scrollTo entry )
keyDecoder : Decode.Decoder String
keyDecoder =
@ -234,6 +331,22 @@ linkBtn disp addr =
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 disp act =
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.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)
@ -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 =
[ 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.html