2025-02-16 05:22:06 +00:00
|
|
|
|
module Main exposing (Flags, Model, Msg, Object3d, main)
|
2025-02-13 07:59:47 +00:00
|
|
|
|
|
2025-01-26 09:17:06 +00:00
|
|
|
|
import Angle
|
|
|
|
|
import Array
|
2025-01-25 07:38:18 +00:00
|
|
|
|
import Browser
|
|
|
|
|
import Browser.Events as Events
|
2025-02-13 07:59:47 +00:00
|
|
|
|
import Browser.Navigation as Nav
|
2025-01-26 09:17:06 +00:00
|
|
|
|
import Camera3d
|
2025-02-09 05:41:36 +00:00
|
|
|
|
import Clipboard exposing (copyToClipboard)
|
2025-01-26 09:17:06 +00:00
|
|
|
|
import Color
|
2025-02-13 07:59:47 +00:00
|
|
|
|
import Dict exposing (Dict)
|
2025-01-26 09:17:06 +00:00
|
|
|
|
import Direction3d
|
2025-01-25 07:38:18 +00:00
|
|
|
|
import Element exposing (..)
|
|
|
|
|
import Element.Background as Background
|
2025-02-09 05:41:36 +00:00
|
|
|
|
import Element.Border as Border
|
2025-01-25 07:38:18 +00:00
|
|
|
|
import Element.Font as Font
|
2025-02-09 05:41:36 +00:00
|
|
|
|
import Element.Input exposing (button)
|
2025-02-13 07:59:47 +00:00
|
|
|
|
import Html exposing (Html)
|
2025-02-10 06:56:10 +00:00
|
|
|
|
import Html.Attributes
|
2025-02-01 08:44:59 +00:00
|
|
|
|
import Http
|
2025-02-10 06:56:10 +00:00
|
|
|
|
import Json.Decode as Decode
|
2025-01-26 09:17:06 +00:00
|
|
|
|
import Length
|
2025-02-15 06:18:58 +00:00
|
|
|
|
import List exposing (length)
|
|
|
|
|
import List.Extra exposing (getAt)
|
2025-01-26 09:17:06 +00:00
|
|
|
|
import Obj.Decode
|
|
|
|
|
import Pixels
|
|
|
|
|
import Point3d exposing (Point3d)
|
|
|
|
|
import Scene3d exposing (Entity)
|
|
|
|
|
import Scene3d.Material as Material
|
|
|
|
|
import Scene3d.Mesh as Mesh
|
2025-02-10 06:56:10 +00:00
|
|
|
|
import Scroll exposing (scrollTo)
|
2025-01-26 09:17:06 +00:00
|
|
|
|
import Simple.Animation as Animation exposing (Animation)
|
|
|
|
|
import Simple.Animation.Animated as Animated
|
|
|
|
|
import Simple.Animation.Property as P
|
|
|
|
|
import Task
|
|
|
|
|
import Time
|
|
|
|
|
import TriangularMesh exposing (TriangularMesh)
|
2025-02-13 07:59:47 +00:00
|
|
|
|
import Url
|
2025-01-26 09:17:06 +00:00
|
|
|
|
import Viewpoint3d
|
|
|
|
|
import WebGL.Texture
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getMesh : Cmd Msg
|
|
|
|
|
getMesh =
|
|
|
|
|
Http.get
|
|
|
|
|
{ url = "../assets/3d/macg/macgregor.obj.txt"
|
|
|
|
|
, expect = Obj.Decode.expectObj GotMesh Length.meters Obj.Decode.texturedTriangles
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
getTexture : Cmd Msg
|
|
|
|
|
getTexture =
|
|
|
|
|
Material.loadWith Material.nearestNeighborFiltering "../assets/3d/macg/image0.jpg" |> Task.attempt GotTexture
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
animatedUi :
|
|
|
|
|
(List (Attribute msg) -> children -> Element msg)
|
|
|
|
|
-> Animation
|
|
|
|
|
-> List (Attribute msg)
|
|
|
|
|
-> children
|
|
|
|
|
-> Element msg
|
|
|
|
|
animatedUi =
|
|
|
|
|
Animated.ui
|
|
|
|
|
{ behindContent = Element.behindContent
|
|
|
|
|
, htmlAttribute = Element.htmlAttribute
|
|
|
|
|
, html = Element.html
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
animatedEl : Animation -> List (Element.Attribute msg) -> Element msg -> Element msg
|
|
|
|
|
animatedEl =
|
|
|
|
|
animatedUi Element.el
|
|
|
|
|
|
|
|
|
|
|
2025-01-25 07:38:18 +00:00
|
|
|
|
main : Program Flags Model Msg
|
|
|
|
|
main =
|
2025-02-13 07:59:47 +00:00
|
|
|
|
Browser.application { init = init, update = update, subscriptions = subscribe, view = view, onUrlRequest = Request, onUrlChange = Load }
|
2025-01-25 07:38:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type alias Model =
|
2025-02-15 06:18:58 +00:00
|
|
|
|
{ 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
|
|
|
|
|
}
|
2025-01-25 07:38:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type alias Flags =
|
|
|
|
|
( Int, Int )
|
|
|
|
|
|
|
|
|
|
|
2025-02-13 07:59:47 +00:00
|
|
|
|
init : Flags -> Url.Url -> Nav.Key -> ( Model, Cmd Msg )
|
|
|
|
|
init flags url key =
|
2025-02-01 08:44:59 +00:00
|
|
|
|
let
|
|
|
|
|
( width, height ) =
|
|
|
|
|
flags
|
|
|
|
|
in
|
2025-02-15 06:18:58 +00:00
|
|
|
|
( { 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 ]
|
|
|
|
|
)
|
2025-01-26 09:17:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type alias Object3d =
|
|
|
|
|
TriangularMesh { position : Point3d Length.Meters Obj.Decode.ObjCoordinates, uv : ( Float, Float ) }
|
2025-01-25 07:38:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
type Msg
|
|
|
|
|
= Resize Int Int
|
2025-02-13 07:59:47 +00:00
|
|
|
|
| Request Browser.UrlRequest
|
|
|
|
|
| Load Url.Url
|
2025-01-26 09:17:06 +00:00
|
|
|
|
| GotMesh (Result Http.Error Object3d)
|
|
|
|
|
| GotTexture (Result WebGL.Texture.Error (Material.Texture Color.Color))
|
|
|
|
|
| Rotate Time.Posix
|
2025-02-13 05:11:07 +00:00
|
|
|
|
| Copy String String
|
2025-02-10 06:56:10 +00:00
|
|
|
|
| Key String
|
|
|
|
|
| Scroll String
|
2025-02-15 06:18:58 +00:00
|
|
|
|
| ScrollToEntry String
|
2025-01-25 07:38:18 +00:00
|
|
|
|
|
|
|
|
|
|
2025-02-09 05:54:29 +00:00
|
|
|
|
modulo : Float -> Float -> Float
|
|
|
|
|
modulo a b =
|
|
|
|
|
b - toFloat (floor (b / a)) * a
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
canonicalize : Float -> Float
|
|
|
|
|
canonicalize angle =
|
|
|
|
|
modulo 360 angle
|
|
|
|
|
|
|
|
|
|
|
2025-02-15 06:18:58 +00:00
|
|
|
|
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
|
|
|
|
|
|
|
|
|
|
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 ->
|
2025-02-16 05:22:06 +00:00
|
|
|
|
let
|
|
|
|
|
p : Float
|
|
|
|
|
p =
|
|
|
|
|
frac (t * toFloat n)
|
|
|
|
|
in
|
2025-02-15 06:18:58 +00:00
|
|
|
|
-- blend both colors
|
|
|
|
|
blend p color1 color2
|
|
|
|
|
|
|
|
|
|
|
2025-01-25 07:38:18 +00:00
|
|
|
|
update : Msg -> Model -> ( Model, Cmd Msg )
|
|
|
|
|
update msg model =
|
2025-01-26 09:17:06 +00:00
|
|
|
|
let
|
|
|
|
|
wrap : Model -> ( Model, Cmd Msg )
|
|
|
|
|
wrap data =
|
|
|
|
|
( data, Cmd.none )
|
|
|
|
|
|
|
|
|
|
pass : ( Model, Cmd Msg )
|
|
|
|
|
pass =
|
|
|
|
|
wrap model
|
|
|
|
|
in
|
2025-01-25 07:38:18 +00:00
|
|
|
|
case msg of
|
|
|
|
|
Resize width height ->
|
2025-01-26 09:17:06 +00:00
|
|
|
|
wrap { model | w = width, h = height }
|
|
|
|
|
|
2025-02-13 07:59:47 +00:00
|
|
|
|
Request req ->
|
|
|
|
|
case req of
|
|
|
|
|
Browser.Internal url ->
|
|
|
|
|
( model, Nav.pushUrl model.key (Url.toString url) )
|
|
|
|
|
|
|
|
|
|
Browser.External href ->
|
|
|
|
|
( model, Nav.load href )
|
|
|
|
|
|
|
|
|
|
Load url ->
|
|
|
|
|
wrap { model | url = url }
|
|
|
|
|
|
2025-01-26 09:17:06 +00:00
|
|
|
|
GotMesh response ->
|
|
|
|
|
case response of
|
|
|
|
|
Err _ ->
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
Ok object ->
|
|
|
|
|
wrap { model | mesh = Just object }
|
|
|
|
|
|
|
|
|
|
GotTexture result ->
|
|
|
|
|
case result of
|
|
|
|
|
Err _ ->
|
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
Ok texture ->
|
|
|
|
|
wrap
|
|
|
|
|
{ model
|
|
|
|
|
| textures =
|
|
|
|
|
Just (Material.texturedMatte texture)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
Rotate time ->
|
2025-02-13 07:59:47 +00:00
|
|
|
|
wrap
|
|
|
|
|
{ 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)
|
2025-02-15 06:18:58 +00:00
|
|
|
|
, mainColor = blender (frac (toFloat (Time.posixToMillis time) / 3500))
|
|
|
|
|
, show = modBy 4000 (Time.posixToMillis time) > 2000
|
2025-02-13 07:59:47 +00:00
|
|
|
|
}
|
2025-01-25 07:38:18 +00:00
|
|
|
|
|
2025-02-13 05:11:07 +00:00
|
|
|
|
Copy label text ->
|
|
|
|
|
( model, copyToClipboard ( label, text ) )
|
2025-02-09 05:41:36 +00:00
|
|
|
|
|
2025-02-10 06:56:10 +00:00
|
|
|
|
Key key ->
|
|
|
|
|
( { model | last = key }, Cmd.none )
|
|
|
|
|
|
|
|
|
|
Scroll loc ->
|
|
|
|
|
( model, scrollTo loc )
|
|
|
|
|
|
2025-02-15 06:18:58 +00:00
|
|
|
|
ScrollToEntry entry ->
|
|
|
|
|
( { model | entry = entry }, scrollTo entry )
|
|
|
|
|
|
2025-02-10 06:56:10 +00:00
|
|
|
|
|
|
|
|
|
keyDecoder : Decode.Decoder String
|
|
|
|
|
keyDecoder =
|
|
|
|
|
Decode.field "key" Decode.string
|
|
|
|
|
|
2025-01-25 07:38:18 +00:00
|
|
|
|
|
|
|
|
|
subscribe : Model -> Sub Msg
|
|
|
|
|
subscribe _ =
|
2025-01-26 09:17:06 +00:00
|
|
|
|
Sub.batch
|
|
|
|
|
[ Events.onResize Resize
|
2025-02-10 06:56:10 +00:00
|
|
|
|
, Events.onKeyPress (Decode.map Key keyDecoder)
|
2025-01-26 09:17:06 +00:00
|
|
|
|
, Time.every (1000 / 30) Rotate
|
|
|
|
|
]
|
2025-01-25 07:38:18 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vw : Model -> Float -> Float
|
|
|
|
|
vw model percent =
|
|
|
|
|
Basics.toFloat model.w * percent / 100
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vh : Model -> Float -> Float
|
|
|
|
|
vh model percent =
|
|
|
|
|
Basics.toFloat model.h * percent / 100
|
|
|
|
|
|
|
|
|
|
|
2025-02-09 05:41:36 +00:00
|
|
|
|
white : Color
|
|
|
|
|
white =
|
|
|
|
|
rgb 255 255 255
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
black : Color
|
|
|
|
|
black =
|
|
|
|
|
rgb 0 0 0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
btnStyle : List (Attribute msg)
|
|
|
|
|
btnStyle =
|
|
|
|
|
[ padding 10
|
2025-02-09 08:58:01 +00:00
|
|
|
|
, Font.color white
|
2025-02-09 05:41:36 +00:00
|
|
|
|
, Font.family [ Font.typeface "Rubik" ]
|
|
|
|
|
, Font.semiBold
|
|
|
|
|
, Font.size 20
|
|
|
|
|
, Border.width 2
|
|
|
|
|
, Border.color white
|
|
|
|
|
, mouseOver [ Background.color white, Font.color black ]
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
linkBtn : String -> String -> Element msg
|
|
|
|
|
linkBtn disp addr =
|
|
|
|
|
newTabLink btnStyle { url = addr, label = text (String.toUpper disp) }
|
|
|
|
|
|
|
|
|
|
|
2025-02-15 06:18:58 +00:00
|
|
|
|
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
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-02-09 05:41:36 +00:00
|
|
|
|
btn : String -> Msg -> Element Msg
|
|
|
|
|
btn disp act =
|
|
|
|
|
button btnStyle { onPress = Just act, label = text (String.toUpper disp) }
|
|
|
|
|
|
|
|
|
|
|
2025-02-09 06:05:58 +00:00
|
|
|
|
vw2pt : Model -> Float -> Int
|
|
|
|
|
vw2pt model ratio =
|
|
|
|
|
(round << vw model) ratio
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vw2px : Model -> Float -> Length
|
|
|
|
|
vw2px model ratio =
|
|
|
|
|
px (vw2pt model ratio)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vh2pt : Model -> Float -> Int
|
|
|
|
|
vh2pt model ratio =
|
|
|
|
|
(round << vh model) ratio
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
vh2px : Model -> Float -> Length
|
|
|
|
|
vh2px model ratio =
|
|
|
|
|
px (vh2pt model ratio)
|
|
|
|
|
|
|
|
|
|
|
2025-02-09 08:58:01 +00:00
|
|
|
|
heading : List (Attr () msg)
|
|
|
|
|
heading =
|
|
|
|
|
[ Font.color white
|
|
|
|
|
, Font.family [ Font.typeface "Imbue" ]
|
|
|
|
|
, Font.size 96
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
subheading : List (Attr () msg)
|
|
|
|
|
subheading =
|
|
|
|
|
[ Font.color white
|
|
|
|
|
, Font.family [ Font.typeface "Imbue" ]
|
|
|
|
|
, Font.size 72
|
|
|
|
|
, width (px 600)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
bodyText : List (Attr () msg)
|
|
|
|
|
bodyText =
|
|
|
|
|
[ Font.color white
|
|
|
|
|
, Font.family [ Font.typeface "Inter" ]
|
|
|
|
|
, Font.size 20
|
|
|
|
|
, width (px 600)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
page : Model -> List (Attr () msg)
|
|
|
|
|
page model =
|
|
|
|
|
[ width fill
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, spacing (vh2pt model -100)
|
|
|
|
|
, Background.color black
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
fullImage : Model -> List (Attr () msg)
|
|
|
|
|
fullImage model =
|
|
|
|
|
[ width fill
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, alpha 0.5
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pageText : Model -> List (Attr () msg)
|
|
|
|
|
pageText model =
|
|
|
|
|
[ spacing 30
|
|
|
|
|
, alignLeft
|
|
|
|
|
, alignTop
|
|
|
|
|
, width (vw2px model 50)
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, paddingEach
|
2025-02-10 06:56:10 +00:00
|
|
|
|
{ top = vh2pt model 50 - 96 * 2
|
2025-02-09 08:58:01 +00:00
|
|
|
|
, bottom = 0
|
|
|
|
|
, left = vw2pt model 10
|
|
|
|
|
, right = 0
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
inlineLink : String -> String -> Element msg
|
|
|
|
|
inlineLink disp addr =
|
|
|
|
|
newTabLink
|
|
|
|
|
[ Font.underline
|
|
|
|
|
, Font.bold
|
|
|
|
|
]
|
|
|
|
|
{ url = addr
|
|
|
|
|
, label = text disp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-02-13 07:59:47 +00:00
|
|
|
|
inlineLinkInt : String -> String -> Element msg
|
|
|
|
|
inlineLinkInt disp addr =
|
|
|
|
|
link
|
|
|
|
|
[ Font.underline
|
|
|
|
|
, Font.bold
|
|
|
|
|
]
|
|
|
|
|
{ url = addr
|
|
|
|
|
, label = text disp
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-02-10 06:56:10 +00:00
|
|
|
|
id : String -> Element.Attribute msg
|
|
|
|
|
id =
|
|
|
|
|
Html.Attributes.id >> Element.htmlAttribute
|
|
|
|
|
|
|
|
|
|
|
2025-02-13 07:59:47 +00:00
|
|
|
|
getPaths : String -> List String
|
|
|
|
|
getPaths base =
|
|
|
|
|
let
|
2025-02-16 05:22:06 +00:00
|
|
|
|
root : String
|
2025-02-13 07:59:47 +00:00
|
|
|
|
root =
|
|
|
|
|
"/" ++ base
|
|
|
|
|
in
|
|
|
|
|
List.map ((++) root) [ "", "/", "/index.html" ]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
itemize : List String -> a -> List ( String, a )
|
|
|
|
|
itemize multikeys entry =
|
|
|
|
|
let
|
2025-02-16 05:22:06 +00:00
|
|
|
|
key : Maybe String
|
2025-02-13 07:59:47 +00:00
|
|
|
|
key =
|
|
|
|
|
List.head multikeys
|
|
|
|
|
in
|
|
|
|
|
case key of
|
|
|
|
|
Just k ->
|
|
|
|
|
( k, entry ) :: itemize (List.drop 1 multikeys) entry
|
|
|
|
|
|
|
|
|
|
Nothing ->
|
|
|
|
|
[]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
sitemap : Dict String (Model -> List (Html Msg))
|
|
|
|
|
sitemap =
|
2025-02-15 06:18:58 +00:00
|
|
|
|
Dict.fromList
|
|
|
|
|
(itemize ([ "/", "/index.html" ] ++ getPaths "src" ++ getPaths "index" ++ getPaths "home") pageHome
|
|
|
|
|
++ itemize (getPaths "entries") pageEntries
|
2025-02-16 05:07:02 +00:00
|
|
|
|
++ itemize (getPaths "about") pageAbout
|
2025-02-15 06:18:58 +00:00
|
|
|
|
)
|
2025-02-13 07:59:47 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
loadUrl : Model -> List (Html Msg)
|
|
|
|
|
loadUrl model =
|
|
|
|
|
let
|
2025-02-16 05:22:06 +00:00
|
|
|
|
req : Maybe (Model -> List (Html Msg))
|
2025-02-13 07:59:47 +00:00
|
|
|
|
req =
|
|
|
|
|
Dict.get model.url.path sitemap
|
|
|
|
|
in
|
|
|
|
|
case req of
|
|
|
|
|
Just builder ->
|
|
|
|
|
builder model
|
|
|
|
|
|
|
|
|
|
Nothing ->
|
|
|
|
|
notFound model
|
|
|
|
|
|
|
|
|
|
|
2025-01-25 07:38:18 +00:00
|
|
|
|
view : Model -> Browser.Document Msg
|
|
|
|
|
view model =
|
|
|
|
|
{ title = "MacGregor House"
|
2025-02-13 07:59:47 +00:00
|
|
|
|
, body = loadUrl model
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
2025-02-14 06:57:46 +00:00
|
|
|
|
menu : Model -> Element Msg
|
|
|
|
|
menu model =
|
|
|
|
|
el
|
|
|
|
|
[ alignLeft
|
|
|
|
|
, alignTop
|
|
|
|
|
, width (vw2px model 25)
|
|
|
|
|
, paddingEach
|
|
|
|
|
{ top = 0
|
|
|
|
|
, bottom = vh2pt model 22.5
|
|
|
|
|
, left = 0
|
|
|
|
|
, right = 0
|
|
|
|
|
}
|
|
|
|
|
, Font.color white
|
|
|
|
|
, Font.family [ Font.typeface "Inter" ]
|
|
|
|
|
, Font.size 18
|
|
|
|
|
]
|
|
|
|
|
(row [ spacing 20 ]
|
|
|
|
|
[ inlineLinkInt "Entries" "/entries"
|
|
|
|
|
, inlineLinkInt "Access" "/about"
|
|
|
|
|
, inlineLinkInt "Colophon" "/colophon"
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2025-02-13 07:59:47 +00:00
|
|
|
|
pageHome : Model -> List (Html Msg)
|
|
|
|
|
pageHome model =
|
|
|
|
|
[ Element.layout
|
|
|
|
|
[ width fill
|
|
|
|
|
]
|
|
|
|
|
(column [ width fill ]
|
|
|
|
|
[ column
|
|
|
|
|
[ width fill
|
|
|
|
|
, height fill
|
|
|
|
|
, spacing (vh2pt model -100)
|
|
|
|
|
, id "zero"
|
|
|
|
|
]
|
|
|
|
|
[ el
|
2025-01-26 09:17:06 +00:00
|
|
|
|
[ width fill
|
2025-02-13 07:59:47 +00:00
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, Background.color black
|
2025-01-26 09:17:06 +00:00
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
Element.none
|
|
|
|
|
, animatedEl crossfadeIn
|
|
|
|
|
[ width fill
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, Background.gradient { angle = 45, steps = [ rgb255 200 0 100, rgb255 100 0 200 ] }
|
|
|
|
|
]
|
|
|
|
|
Element.none
|
|
|
|
|
, animatedEl crossfadeOut
|
|
|
|
|
[ width fill
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, Background.gradient { angle = 45, steps = [ rgb255 0 100 200, rgb255 0 200 100 ] }
|
|
|
|
|
]
|
|
|
|
|
Element.none
|
|
|
|
|
, el
|
|
|
|
|
([ alignLeft
|
|
|
|
|
, alignTop
|
|
|
|
|
, width (vw2px model 50)
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, paddingEach
|
2025-02-14 06:57:46 +00:00
|
|
|
|
{ top = vh2pt model 20 - 96
|
2025-02-13 07:59:47 +00:00
|
|
|
|
, bottom = 0
|
|
|
|
|
, left = vw2pt model 10
|
|
|
|
|
, right = 0
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
++ heading
|
|
|
|
|
)
|
|
|
|
|
(column
|
|
|
|
|
[ spacing 35
|
2025-02-09 08:58:01 +00:00
|
|
|
|
]
|
2025-02-14 06:57:46 +00:00
|
|
|
|
[ menu model
|
|
|
|
|
, text "MacGregor House"
|
2025-02-13 07:59:47 +00:00
|
|
|
|
, column [ spacing 15 ]
|
|
|
|
|
[ row
|
|
|
|
|
[ spacing 15
|
|
|
|
|
]
|
|
|
|
|
[ linkBtn "See events" "https://calendar.google.com/calendar/embed?src=c_c9fb13003264d5becb74cf9ba42a087d8a4a180d927441994458a07ac146eb88%40group.calendar.google.com&ctz=America%2FNew_York"
|
|
|
|
|
, linkBtn "Reserve space" "https://forms.gle/KxFAG65TQuPxdYak8"
|
|
|
|
|
]
|
|
|
|
|
, row [ spacing 15 ]
|
|
|
|
|
[ btn "↓" (Scroll "one")
|
|
|
|
|
, btn "Copy iCal link" (Copy "iCal link" "https://calendar.google.com/calendar/ical/c_c9fb13003264d5becb74cf9ba42a087d8a4a180d927441994458a07ac146eb88%40group.calendar.google.com/public/basic.ics")
|
2025-02-09 08:58:01 +00:00
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
)
|
|
|
|
|
, el
|
|
|
|
|
[ alignRight
|
|
|
|
|
, alignTop
|
|
|
|
|
, width (vw2px model 60)
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, paddingEach
|
|
|
|
|
{ top = vh2pt model 25
|
|
|
|
|
, bottom = vh2pt model 25
|
|
|
|
|
, left = 0
|
|
|
|
|
, right = 0
|
2025-02-09 08:58:01 +00:00
|
|
|
|
}
|
2025-02-13 07:59:47 +00:00
|
|
|
|
]
|
|
|
|
|
(view3D model)
|
|
|
|
|
]
|
|
|
|
|
, column
|
|
|
|
|
(page model ++ [ id "one" ])
|
|
|
|
|
[ image (fullImage model)
|
|
|
|
|
{ src = "../assets/img/tall.jpg"
|
|
|
|
|
, description = "the imposing macgregor superstructure stands tall in defiance of strong winds"
|
|
|
|
|
}
|
|
|
|
|
, column (pageText model)
|
|
|
|
|
[ row [ spacing 10 ]
|
|
|
|
|
[ btn "↑" (Scroll "zero")
|
|
|
|
|
, btn "↓" (Scroll "two")
|
|
|
|
|
]
|
|
|
|
|
, paragraph
|
|
|
|
|
subheading
|
|
|
|
|
[ text "The tallest undergraduate dormitory." ]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "Enrico Fermi once said, \"Before I came here I was confused about this subject. Having listened to your lecture, I am still confused, but on a higher level.\" "
|
|
|
|
|
, inlineLink "Pietro Belluschi" "https://listart.mit.edu/art-artists/macgregor-house-1970"
|
|
|
|
|
, text " attended that lecture."
|
2025-02-09 08:58:01 +00:00
|
|
|
|
]
|
2025-01-26 09:17:06 +00:00
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
]
|
|
|
|
|
, column
|
|
|
|
|
(page model ++ [ id "two" ])
|
|
|
|
|
[ image (fullImage model)
|
|
|
|
|
{ src = "../assets/img/view.jpg"
|
|
|
|
|
, description = "the macgregor pov just hits different"
|
|
|
|
|
}
|
|
|
|
|
, column (pageText model)
|
|
|
|
|
[ row [ spacing 10 ]
|
|
|
|
|
[ btn "↑" (Scroll "one")
|
|
|
|
|
, btn "↓" (Scroll "three")
|
|
|
|
|
]
|
|
|
|
|
, paragraph subheading [ text "Stunning vistas are just the beginning." ]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "A view from MacGregor is like looking down on Earth from the stars. MacGregor's prime waterfront real estate offers breathtaking views of the Charles and the Boston skyline beyond."
|
2025-02-09 08:58:01 +00:00
|
|
|
|
]
|
2025-01-25 07:38:18 +00:00
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
]
|
|
|
|
|
, column
|
|
|
|
|
(page model ++ [ id "three" ])
|
|
|
|
|
[ image (fullImage model)
|
|
|
|
|
{ src = "../assets/img/free.jpg"
|
|
|
|
|
, description = "macgregor is often seen as the gateway to new worlds, especially briggs field"
|
|
|
|
|
}
|
|
|
|
|
, column (pageText model)
|
|
|
|
|
[ row [ spacing 10 ]
|
|
|
|
|
[ btn "↑" (Scroll "two")
|
|
|
|
|
, btn "↓" (Scroll "four")
|
|
|
|
|
]
|
|
|
|
|
, paragraph subheading [ text "Free as in freedom." ]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "This website's source code and infrastructure, the ability to cook, your choice of living community and room assignments—they operate in the public interest of all MacGregorites. You won't get this freedom at many other undergraduate dormitories at MIT."
|
2025-02-09 05:41:36 +00:00
|
|
|
|
]
|
2025-02-09 08:58:01 +00:00
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
]
|
|
|
|
|
, column
|
|
|
|
|
(page model ++ [ id "four" ])
|
|
|
|
|
[ image (fullImage model)
|
|
|
|
|
{ src = "../assets/img/location.jpg"
|
|
|
|
|
, description = "the bright lights of the macgregor high rise shine down upon the glossy snow-covered surface of briggs field"
|
|
|
|
|
}
|
|
|
|
|
, column (pageText model)
|
|
|
|
|
[ row [ spacing 10 ]
|
|
|
|
|
[ btn "↑" (Scroll "three")
|
|
|
|
|
, btn "↓" (Scroll "five")
|
|
|
|
|
]
|
|
|
|
|
, paragraph subheading [ text "Nestled between Kendall and Cambridgeport." ]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "Between "
|
|
|
|
|
, inlineLink "the innovative spirit of Kendall" "https://kendallsquare.org/kendalls-history-orientation/"
|
|
|
|
|
, text " and the industrial crossroads of Cambridgeport, there is a place—on a tiny stretch of street called Amherst Alley—that fills the quiet void with a voracious intellectual appetite and an unparalleled creative vision."
|
2025-02-09 08:58:01 +00:00
|
|
|
|
]
|
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
]
|
|
|
|
|
, column
|
|
|
|
|
(page model ++ [ id "five" ])
|
|
|
|
|
[ image (fullImage model)
|
|
|
|
|
{ src = "../assets/img/brick.jpg"
|
|
|
|
|
, description = "multicolored bricks shine in the limelight of macgregorian festivities"
|
|
|
|
|
}
|
|
|
|
|
, column (pageText model)
|
|
|
|
|
[ row [ spacing 10 ]
|
|
|
|
|
[ btn "↑" (Scroll "four")
|
|
|
|
|
, btn "↓" (Scroll "six")
|
|
|
|
|
]
|
|
|
|
|
, paragraph subheading [ text "We like the ", el [ Font.bold, Font.size 96 ] (text "brick"), text "." ]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "The bricks are everywhere—by far the most recognizable feature of MacGregor. You'll find them protecting the building's exterior from harsh Bostonian winters, lining its fabled corridors, and in your room, as much an architectural statement as they are a testament to the people of MacGregor."
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
, column
|
|
|
|
|
(page model ++ [ id "six" ])
|
|
|
|
|
[ image (fullImage model)
|
|
|
|
|
{ src = "../assets/img/belong.jpg"
|
|
|
|
|
, description = "the cultural murals of macgregor breathe life into its ancient pedestrian thoroughfares"
|
|
|
|
|
}
|
|
|
|
|
, column (pageText model)
|
|
|
|
|
[ row [ spacing 10 ]
|
|
|
|
|
[ btn "↑" (Scroll "five")
|
|
|
|
|
, btn "Top" (Scroll "zero")
|
|
|
|
|
]
|
|
|
|
|
, paragraph subheading [ text "You belong here." ]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "MacGregor's greatness is the greatness of its people. Living in MacGregor inevitably connects you to its people and its culture—one of the most diverse, unique, and historic of any MIT dorm—spanning nine entries, countless murals and traditions, and a couple hundred current residents."
|
2025-02-09 05:41:36 +00:00
|
|
|
|
]
|
2025-02-09 08:58:01 +00:00
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2025-02-16 05:07:02 +00:00
|
|
|
|
pageAbout : Model -> List (Html Msg)
|
|
|
|
|
pageAbout 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 = 45, steps = [ rgb255 255 0 140, rgb255 40 0 255 ] }
|
|
|
|
|
]
|
|
|
|
|
Element.none
|
|
|
|
|
, animatedEl crossfadeOut
|
|
|
|
|
[ width fill
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, Background.gradient { angle = 0, steps = [ rgb255 0 140 255, rgb255 0 150 25 ] }
|
|
|
|
|
]
|
|
|
|
|
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 "Access"
|
|
|
|
|
, column [ spacing 15 ]
|
|
|
|
|
[ row
|
|
|
|
|
[ spacing 15 ]
|
|
|
|
|
[ linkBtnInt "← Back" "/"
|
|
|
|
|
, btn "Directions" (Scroll "one")
|
|
|
|
|
]
|
|
|
|
|
, row [ spacing 15 ]
|
|
|
|
|
[ btn "Facilities" (Scroll "two")
|
|
|
|
|
, btn "Web Accessibility" (Scroll "three")
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
, el
|
|
|
|
|
[ alignRight
|
|
|
|
|
, alignTop
|
|
|
|
|
, width (vw2px model 60)
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, paddingEach
|
|
|
|
|
{ top = vh2pt model 25
|
|
|
|
|
, bottom = vh2pt model 25
|
|
|
|
|
, left = 0
|
|
|
|
|
, right = 0
|
|
|
|
|
}
|
|
|
|
|
]
|
|
|
|
|
(view3DWalk model)
|
|
|
|
|
]
|
|
|
|
|
, column
|
|
|
|
|
(page model ++ [ id "one" ])
|
|
|
|
|
[ image (fullImage model)
|
|
|
|
|
{ src = "../assets/img/map.png"
|
|
|
|
|
, description = "mit campus map with pin at macgregor house"
|
|
|
|
|
}
|
|
|
|
|
, column (pageText model)
|
|
|
|
|
[ row [ spacing 10 ]
|
|
|
|
|
[ btn "↑" (Scroll "zero")
|
|
|
|
|
, btn "↓" (Scroll "two")
|
|
|
|
|
]
|
|
|
|
|
, paragraph
|
|
|
|
|
subheading
|
|
|
|
|
[ text "Directions" ]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "MacGregor's address is "
|
|
|
|
|
, paragraph [ Font.bold ] [ text "450 Memorial Drive, Cambridge, MA 02139" ]
|
|
|
|
|
, text ". It is building number "
|
|
|
|
|
, paragraph [ Font.bold ] [ text "W61" ]
|
|
|
|
|
, text " on the "
|
|
|
|
|
, inlineLink "MIT campus" "https://whereis.mit.edu/?go=W61"
|
|
|
|
|
, text ". See the "
|
|
|
|
|
, inlineLink "OpenStreetMaps identifier" "https://www.openstreetmap.org/way/1246607679"
|
|
|
|
|
, text " for directions and more information. If you are mailing a package to a MacGregor resident, follow the directions listed on the "
|
|
|
|
|
, inlineLink "MIT DSL site" "https://studentlife.mit.edu/housing/undergraduate-housing/mailing-shipping-information"
|
|
|
|
|
, text "."
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
, column
|
|
|
|
|
(page model ++ [ id "two" ])
|
|
|
|
|
[ image (fullImage model)
|
|
|
|
|
{ src = "../assets/img/night.jpg"
|
|
|
|
|
, description = "looking out into the starry night from the second floor patio"
|
|
|
|
|
}
|
|
|
|
|
, column (pageText model)
|
|
|
|
|
[ row [ spacing 10 ]
|
|
|
|
|
[ btn "↑" (Scroll "one")
|
|
|
|
|
, btn "↓" (Scroll "three")
|
|
|
|
|
]
|
|
|
|
|
, paragraph subheading [ text "Facilities and Information" ]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "MacGregor's main facilities are all located on the ground floor. They include a central courtyard, conference/seminar room, music practice room, two laundry rooms, gym, ice machine, game room with a black and white and color printer and an Athena computing cluster, and dining room with a dance studio. All of these facilities are available to residents at any time except when they are being used for "
|
|
|
|
|
, inlineLink "events" "https://calendar.google.com/calendar/embed?src=c_c9fb13003264d5becb74cf9ba42a087d8a4a180d927441994458a07ac146eb88%40group.calendar.google.com&ctz=America%2FNew_York"
|
|
|
|
|
, text "."
|
|
|
|
|
]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "You can ask the front desk for a key to access the music practice room. Printers in MacGregor can be located "
|
|
|
|
|
, inlineLink "here" "https://print.mit.edu"
|
|
|
|
|
, text " (search for W61). Laundry uses the "
|
|
|
|
|
, inlineLink "CSCGo app" "https://mycscgo.com/laundry"
|
|
|
|
|
, text " for payments (approximately $1.25 for a single wash or dry cycle). You can learn more about MacGregor "
|
|
|
|
|
, inlineLink "here" "https://mitguidetoresidences.mit.edu/residences/macgregor-house"
|
|
|
|
|
, text "."
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
, column
|
|
|
|
|
(page model ++ [ id "three" ])
|
|
|
|
|
[ image (fullImage model)
|
|
|
|
|
{ src = "../assets/img/sunrise.jpg"
|
|
|
|
|
, description = "sunrise over back bay, boston, as seen from macgregor south side entrance"
|
|
|
|
|
}
|
|
|
|
|
, column (pageText model)
|
|
|
|
|
[ row [ spacing 10 ]
|
|
|
|
|
[ btn "↑" (Scroll "two")
|
|
|
|
|
, btn "Top" (Scroll "zero")
|
|
|
|
|
]
|
|
|
|
|
, paragraph subheading [ text "Accessibility is our highest priority." ]
|
|
|
|
|
, paragraph bodyText
|
|
|
|
|
[ text "If you're experiencing issues viewing or interacting with parts of this website, please do not hesitate to "
|
|
|
|
|
, inlineLink "contact the webmaster" "mailto:ananthv@mit.edu"
|
|
|
|
|
, text " at any time. We strive to make the story of MacGregor widely accessible to the general public, and we value your input and community support in achieving that goal."
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2025-02-15 06:18:58 +00:00
|
|
|
|
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
|
2025-02-16 03:48:43 +00:00
|
|
|
|
[ text "Welcome to Bentry." ]
|
2025-02-15 06:18:58 +00:00
|
|
|
|
, 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")
|
2025-02-16 05:07:02 +00:00
|
|
|
|
, btn "Top" (ScrollToEntry "zero")
|
2025-02-15 06:18:58 +00:00
|
|
|
|
]
|
|
|
|
|
, 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
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
2025-02-13 07:59:47 +00:00
|
|
|
|
notFound : Model -> List (Html Msg)
|
|
|
|
|
notFound model =
|
|
|
|
|
[ Element.layout
|
|
|
|
|
[ width fill ]
|
|
|
|
|
(column [ width fill ]
|
|
|
|
|
[ column
|
|
|
|
|
[ width fill
|
|
|
|
|
, height fill
|
|
|
|
|
, spacing (vh2pt model -100)
|
|
|
|
|
]
|
|
|
|
|
[ el
|
|
|
|
|
[ width fill
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, Background.color black
|
|
|
|
|
]
|
|
|
|
|
Element.none
|
|
|
|
|
, animatedEl crossfadeIn
|
|
|
|
|
[ width fill
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, Background.gradient { angle = 45, steps = [ rgb255 100 200 0, rgb255 200 100 0 ] }
|
|
|
|
|
]
|
|
|
|
|
Element.none
|
|
|
|
|
, animatedEl crossfadeOut
|
|
|
|
|
[ width fill
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, Background.gradient { angle = 45, steps = [ rgb255 0 150 50, rgb255 0 50 150 ] }
|
|
|
|
|
]
|
|
|
|
|
Element.none
|
|
|
|
|
, el
|
|
|
|
|
([ alignLeft
|
|
|
|
|
, alignTop
|
|
|
|
|
, width (vw2px model 50)
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, paddingEach
|
|
|
|
|
{ top = vh2pt model 35 - 96
|
|
|
|
|
, bottom = 0
|
|
|
|
|
, left = vw2pt model 10
|
|
|
|
|
, right = 0
|
2025-01-26 09:17:06 +00:00
|
|
|
|
}
|
2025-02-13 07:59:47 +00:00
|
|
|
|
]
|
|
|
|
|
++ heading
|
|
|
|
|
)
|
|
|
|
|
(column
|
|
|
|
|
[ spacing 35
|
|
|
|
|
]
|
|
|
|
|
[ text "Error! 404."
|
|
|
|
|
, paragraph (bodyText ++ [ Font.size 24 ])
|
|
|
|
|
[ text "We went all the way up to A entry, then back down and around to J, yet no trace of this page could be found. Perhaps it's in 𝑖 entry?" ]
|
|
|
|
|
, paragraph (bodyText ++ [ width (vw2px model 33) ])
|
|
|
|
|
[ text "You were likely redirected here by a link to a page on the old website, the last known archive of which can be found "
|
|
|
|
|
, inlineLink "here" "https://web.archive.org/web/20170529064230/http://macgregor.mit.edu/"
|
|
|
|
|
, text ". Unless you want to conduct research on ancient MIT traditions, you can probably find what you're looking for on the new "
|
|
|
|
|
, inlineLinkInt "main page" "/src"
|
|
|
|
|
, text ". If you believe this page really is missing, "
|
|
|
|
|
, inlineLink "contact the webmaster" "mailto:ananthv@mit.edu"
|
|
|
|
|
, text "."
|
2025-02-09 08:58:01 +00:00
|
|
|
|
]
|
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
)
|
|
|
|
|
, el
|
|
|
|
|
[ alignRight
|
|
|
|
|
, alignTop
|
|
|
|
|
, width (vw2px model 60)
|
|
|
|
|
, height (vh2px model 100)
|
|
|
|
|
, paddingEach
|
|
|
|
|
{ top = vh2pt model 25
|
|
|
|
|
, bottom = vh2pt model 25
|
|
|
|
|
, left = 0
|
|
|
|
|
, right = 0
|
|
|
|
|
}
|
2025-01-26 09:17:06 +00:00
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
(view3DTower model)
|
2025-01-26 09:17:06 +00:00
|
|
|
|
]
|
2025-02-13 07:59:47 +00:00
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
]
|
2025-01-26 09:17:06 +00:00
|
|
|
|
|
|
|
|
|
|
|
|
|
|
pyramidMesh : Mesh.Uniform coordinates
|
|
|
|
|
pyramidMesh =
|
|
|
|
|
let
|
|
|
|
|
-- Define the vertices of our pyramid
|
2025-02-01 08:44:59 +00:00
|
|
|
|
frontLeft : Point3d Length.Meters coordinates
|
2025-01-26 09:17:06 +00:00
|
|
|
|
frontLeft =
|
|
|
|
|
Point3d.centimeters 250 500 0
|
|
|
|
|
|
2025-02-01 08:44:59 +00:00
|
|
|
|
frontRight : Point3d Length.Meters coordinates
|
2025-01-26 09:17:06 +00:00
|
|
|
|
frontRight =
|
|
|
|
|
Point3d.centimeters 400 0 -500
|
|
|
|
|
|
2025-02-01 08:44:59 +00:00
|
|
|
|
backLeft : Point3d Length.Meters coordinates
|
2025-01-26 09:17:06 +00:00
|
|
|
|
backLeft =
|
|
|
|
|
Point3d.centimeters -250 500 -500
|
|
|
|
|
|
2025-02-01 08:44:59 +00:00
|
|
|
|
backRight : Point3d Length.Meters coordinates
|
2025-01-26 09:17:06 +00:00
|
|
|
|
backRight =
|
|
|
|
|
Point3d.centimeters -250 0 0
|
|
|
|
|
|
2025-02-01 08:44:59 +00:00
|
|
|
|
tip : Point3d Length.Meters coordinates
|
2025-01-26 09:17:06 +00:00
|
|
|
|
tip =
|
|
|
|
|
Point3d.centimeters 0 0 500
|
|
|
|
|
|
|
|
|
|
-- Create a TriangularMesh value from an array of vertices and list
|
|
|
|
|
-- of index triples defining faces (see https://package.elm-lang.org/packages/ianmackenzie/elm-triangular-mesh/latest/TriangularMesh#indexed)
|
2025-02-01 08:44:59 +00:00
|
|
|
|
triangularMesh : TriangularMesh (Point3d Length.Meters coordinates)
|
2025-01-26 09:17:06 +00:00
|
|
|
|
triangularMesh =
|
|
|
|
|
TriangularMesh.indexed
|
|
|
|
|
(Array.fromList
|
|
|
|
|
[ frontLeft -- 0
|
|
|
|
|
, frontRight -- 1
|
|
|
|
|
, backLeft -- 2
|
|
|
|
|
, backRight -- 3
|
|
|
|
|
, tip -- 4
|
|
|
|
|
]
|
|
|
|
|
)
|
|
|
|
|
[ ( 1, 0, 4 ) -- front
|
|
|
|
|
, ( 0, 2, 4 ) -- left
|
|
|
|
|
, ( 2, 3, 4 ) -- back
|
|
|
|
|
, ( 3, 1, 4 ) -- right
|
|
|
|
|
, ( 1, 3, 0 ) -- bottom
|
|
|
|
|
, ( 0, 3, 2 ) -- bottom
|
|
|
|
|
]
|
|
|
|
|
in
|
|
|
|
|
-- Create a elm-3d-scene Mesh value from the TriangularMesh; we use
|
|
|
|
|
-- Mesh.indexedFacets so that normal vectors will be generated for each face
|
|
|
|
|
Mesh.indexedFacets triangularMesh
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
view3D : Model -> Element msg
|
|
|
|
|
view3D model =
|
|
|
|
|
Element.html
|
|
|
|
|
(let
|
|
|
|
|
entity : Entity Obj.Decode.ObjCoordinates
|
|
|
|
|
entity =
|
|
|
|
|
case model.mesh of
|
|
|
|
|
Nothing ->
|
|
|
|
|
Scene3d.mesh (Material.matte (Color.rgb255 173 111 101)) pyramidMesh
|
|
|
|
|
|
|
|
|
|
Just mesh ->
|
|
|
|
|
case model.textures of
|
|
|
|
|
Nothing ->
|
|
|
|
|
Scene3d.mesh (Material.matte (Color.rgb255 173 111 101)) (Mesh.texturedFacets mesh)
|
|
|
|
|
|
|
|
|
|
Just textures ->
|
|
|
|
|
Scene3d.mesh textures (Mesh.texturedFacets mesh)
|
|
|
|
|
|
|
|
|
|
camera : Camera3d.Camera3d Length.Meters coordinates
|
|
|
|
|
camera =
|
|
|
|
|
Camera3d.perspective
|
|
|
|
|
{ viewpoint =
|
|
|
|
|
Viewpoint3d.lookAt
|
|
|
|
|
{ focalPoint = Point3d.origin
|
|
|
|
|
, eyePoint =
|
|
|
|
|
let
|
2025-02-01 08:44:59 +00:00
|
|
|
|
theta : Angle.Angle
|
2025-01-26 09:17:06 +00:00
|
|
|
|
theta =
|
|
|
|
|
Angle.degrees 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)) )
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2025-02-15 06:18:58 +00:00
|
|
|
|
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)
|
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
camera : Camera3d.Camera3d Length.Meters coordinates
|
|
|
|
|
camera =
|
|
|
|
|
Camera3d.perspective
|
|
|
|
|
{ viewpoint =
|
|
|
|
|
Viewpoint3d.lookAt
|
|
|
|
|
{ focalPoint = Point3d.origin
|
|
|
|
|
, eyePoint =
|
|
|
|
|
let
|
|
|
|
|
orbit : Float
|
|
|
|
|
orbit =
|
|
|
|
|
case entry of
|
|
|
|
|
"A" ->
|
|
|
|
|
4
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"B" ->
|
|
|
|
|
6
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"C" ->
|
|
|
|
|
6
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"D" ->
|
|
|
|
|
10
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"F" ->
|
|
|
|
|
12
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"G" ->
|
|
|
|
|
10
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"J" ->
|
|
|
|
|
12
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"H" ->
|
|
|
|
|
11
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
_ ->
|
|
|
|
|
7
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
perspective : Angle.Angle
|
|
|
|
|
perspective =
|
|
|
|
|
let
|
|
|
|
|
phi : Float
|
|
|
|
|
phi =
|
|
|
|
|
180 * frac (model.angle / 180)
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
winding : Float
|
|
|
|
|
winding =
|
|
|
|
|
360 * frac (model.angle / 360)
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
direction : Float
|
|
|
|
|
direction =
|
|
|
|
|
abs (180 - winding) / (180 - winding)
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
interp : Float -> Float -> Angle.Angle
|
|
|
|
|
interp a b =
|
|
|
|
|
Angle.degrees
|
|
|
|
|
(if direction > 0 then
|
|
|
|
|
a + (b - a) * phi / 180
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
else
|
|
|
|
|
b + (a - b) * phi / 180
|
|
|
|
|
)
|
|
|
|
|
in
|
|
|
|
|
case entry of
|
|
|
|
|
"J" ->
|
|
|
|
|
interp -45 30
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"E" ->
|
|
|
|
|
interp 30 110
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"F" ->
|
|
|
|
|
interp 140 230
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"G" ->
|
|
|
|
|
interp 230 290
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"H" ->
|
|
|
|
|
interp 290 315
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
_ ->
|
|
|
|
|
Angle.degrees model.angle
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
height : Float
|
|
|
|
|
height =
|
|
|
|
|
case entry of
|
|
|
|
|
"A" ->
|
|
|
|
|
10
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"B" ->
|
|
|
|
|
7
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"C" ->
|
|
|
|
|
5
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
"E" ->
|
|
|
|
|
1
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
2025-02-16 05:22:06 +00:00
|
|
|
|
_ ->
|
|
|
|
|
2
|
2025-02-15 06:18:58 +00:00
|
|
|
|
|
|
|
|
|
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)) )
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2025-02-13 07:59:47 +00:00
|
|
|
|
view3DTower : Model -> Element msg
|
|
|
|
|
view3DTower model =
|
|
|
|
|
Element.html
|
|
|
|
|
(let
|
|
|
|
|
entity : Entity Obj.Decode.ObjCoordinates
|
|
|
|
|
entity =
|
|
|
|
|
case model.mesh of
|
|
|
|
|
Nothing ->
|
|
|
|
|
Scene3d.mesh (Material.matte (Color.rgb255 173 111 101)) pyramidMesh
|
|
|
|
|
|
|
|
|
|
Just mesh ->
|
|
|
|
|
case model.textures of
|
|
|
|
|
Nothing ->
|
|
|
|
|
Scene3d.mesh (Material.matte (Color.rgb255 173 111 101)) (Mesh.texturedFacets mesh)
|
|
|
|
|
|
|
|
|
|
Just textures ->
|
|
|
|
|
Scene3d.mesh textures (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 90
|
|
|
|
|
in
|
|
|
|
|
Point3d.meters (model.radius * Angle.cos theta) model.elevation (model.radius * 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)) )
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2025-02-16 05:07:02 +00:00
|
|
|
|
view3DWalk : Model -> Element msg
|
|
|
|
|
view3DWalk model =
|
|
|
|
|
Element.html
|
|
|
|
|
(let
|
|
|
|
|
entity : Entity Obj.Decode.ObjCoordinates
|
|
|
|
|
entity =
|
|
|
|
|
case model.mesh of
|
|
|
|
|
Nothing ->
|
|
|
|
|
Scene3d.mesh (Material.matte (Color.rgb255 173 111 101)) pyramidMesh
|
|
|
|
|
|
|
|
|
|
Just mesh ->
|
|
|
|
|
case model.textures of
|
|
|
|
|
Nothing ->
|
|
|
|
|
Scene3d.mesh (Material.matte (Color.rgb255 173 111 101)) (Mesh.texturedFacets mesh)
|
|
|
|
|
|
|
|
|
|
Just textures ->
|
|
|
|
|
Scene3d.mesh textures (Mesh.texturedFacets mesh)
|
|
|
|
|
|
|
|
|
|
camera : Camera3d.Camera3d Length.Meters coordinates
|
|
|
|
|
camera =
|
|
|
|
|
let
|
|
|
|
|
phi : Float
|
|
|
|
|
phi =
|
|
|
|
|
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) * phi / 180
|
|
|
|
|
|
|
|
|
|
else
|
|
|
|
|
b + (a - b) * phi / 180
|
|
|
|
|
)
|
|
|
|
|
in
|
|
|
|
|
Camera3d.perspective
|
|
|
|
|
{ viewpoint =
|
|
|
|
|
Viewpoint3d.lookAt
|
|
|
|
|
{ focalPoint = Point3d.origin
|
|
|
|
|
, eyePoint =
|
|
|
|
|
let
|
|
|
|
|
theta : Angle.Angle
|
|
|
|
|
theta =
|
|
|
|
|
interp -90 90
|
|
|
|
|
in
|
|
|
|
|
Point3d.meters (model.radius * Angle.cos theta) model.elevation (model.radius * Angle.sin theta)
|
|
|
|
|
, upDirection = Direction3d.xy (interp (90 + 45) (90 - 45))
|
|
|
|
|
}
|
|
|
|
|
, 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)) )
|
|
|
|
|
}
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
2025-01-26 09:17:06 +00:00
|
|
|
|
crossfadeIn : Animation
|
|
|
|
|
crossfadeIn =
|
|
|
|
|
Animation.fromTo
|
|
|
|
|
{ duration = 2017
|
|
|
|
|
, options = [ Animation.yoyo, Animation.loop ]
|
|
|
|
|
}
|
|
|
|
|
[ P.opacity 0 ]
|
|
|
|
|
[ P.opacity 1 ]
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
crossfadeOut : Animation
|
|
|
|
|
crossfadeOut =
|
|
|
|
|
Animation.fromTo
|
|
|
|
|
{ duration = 2027
|
|
|
|
|
, options = [ Animation.yoyo, Animation.loop ]
|
|
|
|
|
}
|
|
|
|
|
[ P.opacity 1 ]
|
|
|
|
|
[ P.opacity 0 ]
|