elm-ssr 0.2.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +48 -342
  2. package/elm-src/ElmSsr/Island/Sse.elm +151 -0
  3. package/package.json +53 -24
  4. package/{packages/elm-ssr/src → src}/client-runtime/islands.ts +113 -15
  5. package/src/sse.ts +162 -0
  6. package/AGENTS.md +0 -289
  7. package/CHANGELOG.md +0 -87
  8. package/LICENSE +0 -21
  9. package/bun.lock +0 -259
  10. package/docker-compose.yml +0 -33
  11. package/docs/README.md +0 -51
  12. package/docs/backends.md +0 -146
  13. package/docs/cli.md +0 -117
  14. package/docs/effects.md +0 -91
  15. package/docs/getting-started.md +0 -94
  16. package/docs/islands.md +0 -197
  17. package/docs/loaders-and-actions.md +0 -241
  18. package/docs/middleware.md +0 -93
  19. package/docs/migrations.md +0 -143
  20. package/docs/routing.md +0 -108
  21. package/docs/sessions.md +0 -218
  22. package/docs/tasks.md +0 -149
  23. package/docs/testing.md +0 -84
  24. package/elm-ssr.config.json +0 -14
  25. package/examples/basic/elm.json +0 -27
  26. package/examples/basic/migrations/0001_guestbook.down.sql +0 -1
  27. package/examples/basic/migrations/0001_guestbook.sql +0 -10
  28. package/examples/basic/package.json +0 -10
  29. package/examples/basic/runtime.ts +0 -148
  30. package/examples/basic/src/Example/Basic/Islands/Counter.elm +0 -110
  31. package/examples/basic/src/Example/Basic/Islands/Observer.elm +0 -67
  32. package/examples/basic/src/Example/Basic/Islands/Tasks.elm +0 -151
  33. package/examples/basic/src/Example/Basic/Routes/Chart.elm +0 -87
  34. package/examples/basic/src/Example/Basic/Routes/Counter.elm +0 -42
  35. package/examples/basic/src/Example/Basic/Routes/Echo.elm +0 -76
  36. package/examples/basic/src/Example/Basic/Routes/Greet/Name_.elm +0 -37
  37. package/examples/basic/src/Example/Basic/Routes/Guestbook.elm +0 -86
  38. package/examples/basic/src/Example/Basic/Routes/Index.elm +0 -41
  39. package/examples/basic/src/Example/Basic/Routes/NotFound.elm +0 -37
  40. package/examples/basic/src/Example/Basic/Routes/Profile.elm +0 -112
  41. package/examples/basic/src/Example/Basic/Routes/Session.elm +0 -89
  42. package/examples/basic/src/Example/Basic/Routes/Status.elm +0 -90
  43. package/examples/basic/src/Example/Basic/View/Shared.elm +0 -60
  44. package/examples/basic/styles.ts +0 -204
  45. package/examples/basic/worker.ts +0 -3
  46. package/examples/crypto-dashboard/elm.json +0 -30
  47. package/examples/crypto-dashboard/package.json +0 -10
  48. package/examples/crypto-dashboard/runtime.ts +0 -97
  49. package/examples/crypto-dashboard/src/CryptoDashboard/Islands/MarketOverview.elm +0 -204
  50. package/examples/crypto-dashboard/src/CryptoDashboard/Islands/PriceChart.elm +0 -200
  51. package/examples/crypto-dashboard/src/CryptoDashboard/Routes/Index.elm +0 -67
  52. package/examples/crypto-dashboard/src/CryptoDashboard/Routes/NotFound.elm +0 -30
  53. package/examples/crypto-dashboard/src/CryptoDashboard/View/Shared.elm +0 -39
  54. package/examples/crypto-dashboard/styles.ts +0 -23
  55. package/examples/crypto-dashboard/worker.ts +0 -3
  56. package/llms.txt +0 -69
  57. package/packages/elm-ssr/README.md +0 -67
  58. package/packages/elm-ssr/package.json +0 -61
  59. package/scripts/benchmark.mjs +0 -60
  60. package/test/action.test.ts +0 -81
  61. package/test/adapters.test.ts +0 -173
  62. package/test/advanced-robustness.test.ts +0 -75
  63. package/test/app.test.ts +0 -209
  64. package/test/browser-island.test.ts +0 -184
  65. package/test/cli-migrate.test.ts +0 -97
  66. package/test/cli.test.ts +0 -94
  67. package/test/cookies.test.ts +0 -156
  68. package/test/crypto-dashboard.test.ts +0 -35
  69. package/test/effects.test.ts +0 -117
  70. package/test/http.test.ts +0 -50
  71. package/test/integration/redis-postgres.test.ts +0 -174
  72. package/test/island-runtime.test.ts +0 -214
  73. package/test/middleware.test.ts +0 -134
  74. package/test/migrations.test.ts +0 -244
  75. package/test/profile.test.ts +0 -159
  76. package/test/robustness.test.ts +0 -135
  77. package/test/serialize.test.ts +0 -92
  78. package/test/sessions.test.ts +0 -429
  79. package/test/svg.test.ts +0 -65
  80. package/tsconfig.json +0 -20
  81. package/wrangler.jsonc +0 -11
  82. /package/{packages/elm-ssr/bin → bin}/elm-ssr.mjs +0 -0
  83. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Action.elm +0 -0
  84. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Document/Encode.elm +0 -0
  85. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Document/Events.elm +0 -0
  86. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Document.elm +0 -0
  87. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Html/Attributes.elm +0 -0
  88. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Html/Events.elm +0 -0
  89. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Html.elm +0 -0
  90. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Island/Shared.elm +0 -0
  91. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Island.elm +0 -0
  92. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Loader.elm +0 -0
  93. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Page.elm +0 -0
  94. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Route.elm +0 -0
  95. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Runtime.elm +0 -0
  96. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Svg/Attributes.elm +0 -0
  97. /package/{packages/elm-ssr/elm-src → elm-src}/ElmSsr/Svg.elm +0 -0
  98. /package/{packages/elm-ssr/lib → lib}/build.mjs +0 -0
  99. /package/{packages/elm-ssr/lib → lib}/migrate.mjs +0 -0
  100. /package/{packages/elm-ssr/lib → lib}/scaffold.mjs +0 -0
  101. /package/{packages/elm-ssr/lib → lib}/workspace.mjs +0 -0
  102. /package/{packages/elm-ssr/src → src}/app.ts +0 -0
  103. /package/{packages/elm-ssr/src → src}/backends.ts +0 -0
  104. /package/{packages/elm-ssr/src → src}/effects.ts +0 -0
  105. /package/{packages/elm-ssr/src → src}/http.ts +0 -0
  106. /package/{packages/elm-ssr/src → src}/middleware.ts +0 -0
  107. /package/{packages/elm-ssr/src → src}/migrations.ts +0 -0
  108. /package/{packages/elm-ssr/src → src}/protocol.ts +0 -0
  109. /package/{packages/elm-ssr/src → src}/render.ts +0 -0
  110. /package/{packages/elm-ssr/src → src}/request-handler.ts +0 -0
  111. /package/{packages/elm-ssr/src → src}/response-headers.ts +0 -0
  112. /package/{packages/elm-ssr/src → src}/serialize.ts +0 -0
  113. /package/{packages/elm-ssr/src → src}/sessions/crypto.ts +0 -0
  114. /package/{packages/elm-ssr/src → src}/sessions/effects.ts +0 -0
  115. /package/{packages/elm-ssr/src → src}/sessions/index.ts +0 -0
  116. /package/{packages/elm-ssr/src → src}/sessions/middleware.ts +0 -0
  117. /package/{packages/elm-ssr/src → src}/sessions/store.ts +0 -0
  118. /package/{packages/elm-ssr/src → src}/sessions/types.ts +0 -0
  119. /package/{packages/elm-ssr/src → src}/tasks.ts +0 -0
@@ -1,37 +0,0 @@
1
- module Example.Basic.Routes.NotFound exposing (page, action)
2
-
3
- import ElmSsr.Action as Action exposing (Action)
4
- import ElmSsr.Document exposing (Document)
5
- import ElmSsr.Html exposing (a, article, h1, p, text)
6
- import ElmSsr.Html.Attributes exposing (class, href)
7
- import ElmSsr.Loader as Loader exposing (Loader)
8
- import ElmSsr.Page as Page
9
- import ElmSsr.Route exposing (Request)
10
- import Example.Basic.View.Shared as Shared
11
-
12
-
13
- page : Request -> Loader (Document Never)
14
- page _ =
15
- Loader.succeed view
16
-
17
-
18
- action : Request -> Action (Document Never)
19
- action _ =
20
- Action.fail 405 "Method not allowed"
21
-
22
-
23
- view : Document Never
24
- view =
25
- Page.notFound
26
- { title = "Not Found | elm-ssr"
27
- , head = Shared.baseHead
28
- , body =
29
- [ Shared.layout
30
- [ article [ class "panel" ]
31
- [ h1 [] [ text "404" ]
32
- , p [] [ text "This route is not registered in the example app." ]
33
- , a [ class "link", href "/" ] [ text "Back to home" ]
34
- ]
35
- ]
36
- ]
37
- }
@@ -1,112 +0,0 @@
1
- module Example.Basic.Routes.Profile exposing (action, page)
2
-
3
- {-| File-based routing: GET /profile reads the current session via
4
- `Loader.session`; POST /profile?op=login sets it via `Loader.setSession`
5
- (lifted into the action with `Action.fromLoader`); POST /profile?op=logout
6
- calls `Loader.clearSession`. Demonstrates the full session+CSRF round-trip.
7
-
8
- The TS side wires `sessionMiddleware` + `csrfMiddleware` +
9
- `sessionEffects(runner)` in runtime.ts; this Elm module never touches the
10
- underlying cookie itself.
11
- -}
12
-
13
- import ElmSsr.Action as Action exposing (Action)
14
- import ElmSsr.Document exposing (Document)
15
- import ElmSsr.Html exposing (button, form, h1, input, label, p, section, span, text)
16
- import ElmSsr.Html.Attributes as Attr
17
- import ElmSsr.Loader as Loader exposing (Loader)
18
- import ElmSsr.Route as Route exposing (Request)
19
- import Example.Basic.View.Shared as Shared
20
- import Json.Decode as Decode
21
- import Json.Encode as Encode
22
-
23
-
24
- type alias Profile =
25
- { username : String }
26
-
27
-
28
- profileDecoder : Decode.Decoder Profile
29
- profileDecoder =
30
- Decode.map Profile (Decode.field "username" Decode.string)
31
-
32
-
33
- encodeProfile : Profile -> Encode.Value
34
- encodeProfile profile =
35
- Encode.object [ ( "username", Encode.string profile.username ) ]
36
-
37
-
38
- page : Request -> Loader (Document Never)
39
- page _ =
40
- Loader.map2 view (Loader.session profileDecoder) Loader.csrfToken
41
-
42
-
43
- action : Request -> Action (Document Never)
44
- action request =
45
- case Route.query "op" request of
46
- Just "logout" ->
47
- Action.fromLoader Loader.clearSession
48
- |> Action.andThen (\_ -> Action.redirect "/profile")
49
-
50
- _ ->
51
- case Maybe.map String.trim (Route.formValue "username" request) of
52
- Just username ->
53
- if String.isEmpty username then
54
- Action.fail 422 "Username is required."
55
-
56
- else
57
- Action.fromLoader (Loader.setSession (encodeProfile { username = username }))
58
- |> Action.andThen (\_ -> Action.redirect "/profile")
59
-
60
- Nothing ->
61
- Action.fail 422 "Username is required."
62
-
63
-
64
- view : Maybe Profile -> Maybe String -> Document Never
65
- view profile token =
66
- Shared.pageDocument "Profile"
67
- [ section [ Attr.class "panel" ]
68
- (case profile of
69
- Just { username } ->
70
- [ h1 [] [ text "Signed in" ]
71
- , p [] [ text "Hello, ", span [ Attr.class "value" ] [ text username ], text "." ]
72
- , form
73
- [ Attr.method "post", Attr.action "/profile?op=logout" ]
74
- (csrfField token
75
- ++ [ button [ Attr.type_ "submit", Attr.class "button" ] [ text "Sign out" ] ]
76
- )
77
- ]
78
-
79
- Nothing ->
80
- [ h1 [] [ text "Sign in" ]
81
- , p [] [ text "No session yet." ]
82
- , form
83
- [ Attr.method "post", Attr.action "/profile", Attr.class "form" ]
84
- (csrfField token
85
- ++ [ label [ Attr.for "username" ] [ text "Username" ]
86
- , input
87
- [ Attr.id "username"
88
- , Attr.name "username"
89
- , Attr.type_ "text"
90
- , Attr.class "input"
91
- ]
92
- , button [ Attr.type_ "submit", Attr.class "button" ] [ text "Sign in" ]
93
- ]
94
- )
95
- ]
96
- )
97
- ]
98
-
99
-
100
- csrfField : Maybe String -> List (ElmSsr.Html.Node msg)
101
- csrfField token =
102
- case token of
103
- Just value ->
104
- [ input
105
- [ Attr.type_ "hidden"
106
- , Attr.name "_csrf"
107
- , Attr.value value
108
- ]
109
- ]
110
-
111
- Nothing ->
112
- []
@@ -1,89 +0,0 @@
1
- module Example.Basic.Routes.Session exposing (action, page)
2
-
3
- {-| File-based routing: GET /session reads the "session" cookie and shows who
4
- you are; POST /session?op=login sets a hardened session cookie via
5
- `Action.sessionCookie`; POST /session?op=logout clears it. Demonstrates both
6
- sides of the cookie API end-to-end with no client JS.
7
- -}
8
-
9
- import ElmSsr.Action as Action exposing (Action)
10
- import ElmSsr.Document exposing (Document)
11
- import ElmSsr.Html exposing (button, form, h1, input, label, p, section, span, text)
12
- import ElmSsr.Html.Attributes as Attr
13
- import ElmSsr.Loader as Loader exposing (Loader)
14
- import ElmSsr.Route as Route exposing (Request)
15
- import Example.Basic.View.Shared as Shared
16
-
17
-
18
- page : Request -> Loader (Document Never)
19
- page _ =
20
- Loader.getCookie "session"
21
- |> Loader.map view
22
-
23
-
24
- action : Request -> Action (Document Never)
25
- action request =
26
- case Route.query "op" request of
27
- Just "logout" ->
28
- Action.redirect "/session"
29
- |> Action.clearCookie { name = "session", path = Just "/", domain = Nothing }
30
-
31
- _ ->
32
- case Maybe.map String.trim (Route.formValue "username" request) of
33
- Just username ->
34
- if String.isEmpty username then
35
- Action.fail 422 "Username is required."
36
-
37
- else
38
- -- For the example, the username *is* the session id. A real app
39
- -- would mint a server-side session token, store it (KV/D1/Redis),
40
- -- and put the token in the cookie.
41
- Action.redirect "/session"
42
- |> Action.setCookie (devSessionCookie "session" username)
43
-
44
- Nothing ->
45
- Action.fail 422 "Username is required."
46
-
47
-
48
- {-| The example runs over plain HTTP in tests + `wrangler dev`, so we mint a
49
- session cookie with `Secure=False`. Production code uses `Action.sessionCookie`
50
- unchanged.
51
- -}
52
- devSessionCookie : String -> String -> Action.Cookie
53
- devSessionCookie name value =
54
- let
55
- secure =
56
- Action.sessionCookie name value
57
- in
58
- { secure | secure = False }
59
-
60
-
61
- view : Maybe String -> Document Never
62
- view session =
63
- Shared.pageDocument "Session"
64
- [ section [ Attr.class "panel" ]
65
- (case session of
66
- Just user ->
67
- [ h1 [] [ text "Signed in" ]
68
- , p [] [ text "Welcome back, ", span [ Attr.class "value" ] [ text user ], text "." ]
69
- , form [ Attr.method "post", Attr.action "/session?op=logout" ]
70
- [ button [ Attr.type_ "submit", Attr.class "button" ] [ text "Sign out" ] ]
71
- ]
72
-
73
- Nothing ->
74
- [ h1 [] [ text "Sign in" ]
75
- , p [] [ text "No session cookie was sent." ]
76
- , form [ Attr.method "post", Attr.action "/session", Attr.class "form" ]
77
- [ label [ Attr.for "username" ] [ text "Username" ]
78
- , input
79
- [ Attr.id "username"
80
- , Attr.name "username"
81
- , Attr.type_ "text"
82
- , Attr.class "input"
83
- , Attr.attr "autocomplete" "username"
84
- ]
85
- , button [ Attr.type_ "submit", Attr.class "button" ] [ text "Sign in" ]
86
- ]
87
- ]
88
- )
89
- ]
@@ -1,90 +0,0 @@
1
- module Example.Basic.Routes.Status exposing (action, page)
2
-
3
- import ElmSsr.Action as Action exposing (Action)
4
- import ElmSsr.Document exposing (Document)
5
- import ElmSsr.Html exposing (Node, h1, li, p, section, span, text, ul)
6
- import ElmSsr.Html.Attributes exposing (class)
7
- import ElmSsr.Loader as Loader exposing (Loader)
8
- import ElmSsr.Route exposing (Request)
9
- import Example.Basic.View.Shared as Shared
10
- import Json.Decode as Decode
11
- import Json.Encode as Encode
12
-
13
-
14
- type alias Status =
15
- { uptime : String
16
- , region : String
17
- , builds : Int
18
- }
19
-
20
-
21
- page : Request -> Loader (Document Never)
22
- page _ =
23
- Loader.map2 view cachedStatus (Loader.env "GREETING")
24
-
25
-
26
- {-| Read the status from the cache; on a miss, fetch it and populate the cache
27
- (with a 60s TTL). Backend-neutral: cache is KV on Cloudflare, Redis/Map locally.
28
- -}
29
- cachedStatus : Loader Status
30
- cachedStatus =
31
- Loader.cacheGet { key = "status", decoder = decoder }
32
- |> Loader.andThen
33
- (\cached ->
34
- case cached of
35
- Just status ->
36
- Loader.succeed status
37
-
38
- Nothing ->
39
- Loader.fetchJson { url = "app://status", decoder = decoder }
40
- |> Loader.andThen
41
- (\status ->
42
- Loader.cachePut { key = "status", value = encode status, ttlSeconds = Just 60 }
43
- |> Loader.map (\_ -> status)
44
- )
45
- )
46
-
47
-
48
- action : Request -> Action (Document Never)
49
- action _ =
50
- Action.fail 405 "Method not allowed"
51
-
52
-
53
- decoder : Decode.Decoder Status
54
- decoder =
55
- Decode.map3 Status
56
- (Decode.field "uptime" Decode.string)
57
- (Decode.field "region" Decode.string)
58
- (Decode.field "builds" Decode.int)
59
-
60
-
61
- encode : Status -> Encode.Value
62
- encode status =
63
- Encode.object
64
- [ ( "uptime", Encode.string status.uptime )
65
- , ( "region", Encode.string status.region )
66
- , ( "builds", Encode.int status.builds )
67
- ]
68
-
69
-
70
- view : Status -> Maybe String -> Document Never
71
- view data greeting =
72
- Shared.pageDocument "Edge Status"
73
- [ statusSection data greeting
74
- , Shared.featureSection
75
- ]
76
-
77
-
78
- statusSection : Status -> Maybe String -> Node msg
79
- statusSection data greeting =
80
- section [ class "panel" ]
81
- [ span [ class "eyebrow" ] [ text "Loader page (server only)" ]
82
- , h1 [] [ text "Edge status" ]
83
- , p [] [ text "This page has no Model and no Msg. A Loader read the cache (filling it from a fetch on a miss) and read an env value on the server, then rendered once with no client runtime." ]
84
- , ul [ class "list" ]
85
- [ li [] [ text ("Uptime: " ++ data.uptime) ]
86
- , li [] [ text ("Region: " ++ data.region) ]
87
- , li [] [ text ("Builds: " ++ String.fromInt data.builds) ]
88
- , li [] [ text ("Env GREETING: " ++ Maybe.withDefault "—" greeting) ]
89
- ]
90
- ]
@@ -1,60 +0,0 @@
1
- module Example.Basic.View.Shared exposing (baseHead, featureSection, layout, pageDocument)
2
-
3
- import ElmSsr.Document exposing (Document)
4
- import ElmSsr.Html exposing (Node, a, article, div, h2, h3, header, main_, nav, p, section, span, text)
5
- import ElmSsr.Html.Attributes exposing (class, href)
6
- import ElmSsr.Page as Page
7
-
8
-
9
- pageDocument : String -> List (Node msg) -> Document msg
10
- pageDocument pageTitle sections =
11
- Page.page
12
- { title = pageTitle ++ " | elm-ssr"
13
- , head = baseHead
14
- , body = [ layout sections ]
15
- }
16
-
17
-
18
- baseHead : List (Node msg)
19
- baseHead =
20
- [ Page.metaCharset "utf-8"
21
- , Page.metaViewport "width=device-width, initial-scale=1"
22
- , Page.metaName "description" "Elm SSR library prototype with client-side updates."
23
- , Page.stylesheet "/styles.css"
24
- ]
25
-
26
-
27
- layout : List (Node msg) -> Node msg
28
- layout sections =
29
- div [ class "shell" ]
30
- [ header [ class "topbar" ]
31
- [ div [ class "brand" ]
32
- [ span [ class "brand-badge" ] [ text "elm-ssr" ]
33
- , h2 [ class "brand-title" ] [ text "Library Example" ]
34
- ]
35
- , nav [ class "nav" ]
36
- [ a [ class "nav-link", href "/" ] [ text "Home" ]
37
- , a [ class "nav-link", href "/status" ] [ text "Status" ]
38
- , a [ class "nav-link", href "/counter" ] [ text "Counter" ]
39
- ]
40
- ]
41
- , main_ [ class "content" ] sections
42
- ]
43
-
44
-
45
- featureSection : Node msg
46
- featureSection =
47
- section [ class "grid" ]
48
- [ article [ class "panel" ]
49
- [ h3 [] [ text "Page" ]
50
- , p [] [ text "Route + Loader + view. Stateless, no client runtime, Document Never enforces it." ]
51
- ]
52
- , article [ class "panel" ]
53
- [ h3 [] [ text "Loader" ]
54
- , p [] [ text "Describe the data a route needs. The Worker runs the IO and feeds results back, fully typed." ]
55
- ]
56
- , article [ class "panel" ]
57
- [ h3 [] [ text "Interactive" ]
58
- , p [] [ text "Opt into the MVU loop only where you need it. Events patch the server-rendered DOM in place." ]
59
- ]
60
- ]
@@ -1,204 +0,0 @@
1
- export const stylesheet = `
2
- :root {
3
- color-scheme: light;
4
- --bg: #f4efe7;
5
- --panel: rgba(255, 251, 244, 0.9);
6
- --ink: #1d1b19;
7
- --muted: #5a554f;
8
- --accent: #155848;
9
- --accent-soft: #dcebe5;
10
- --line: rgba(29, 27, 25, 0.08);
11
- --shadow: 0 18px 60px rgba(21, 88, 72, 0.14);
12
- }
13
-
14
- * {
15
- box-sizing: border-box;
16
- }
17
-
18
- html,
19
- body {
20
- margin: 0;
21
- min-height: 100%;
22
- }
23
-
24
- body {
25
- background:
26
- radial-gradient(circle at top left, rgba(21, 88, 72, 0.12), transparent 34%),
27
- linear-gradient(180deg, #fbf8f2 0%, var(--bg) 100%);
28
- color: var(--ink);
29
- font-family: "Iowan Old Style", "Palatino Linotype", "Book Antiqua", serif;
30
- }
31
-
32
- a {
33
- color: inherit;
34
- }
35
-
36
- .shell {
37
- width: min(1120px, calc(100vw - 32px));
38
- margin: 0 auto;
39
- padding: 24px 0 64px;
40
- }
41
-
42
- .topbar {
43
- display: flex;
44
- align-items: center;
45
- justify-content: space-between;
46
- gap: 18px;
47
- padding: 10px 0 24px;
48
- }
49
-
50
- .brand {
51
- display: flex;
52
- align-items: center;
53
- gap: 14px;
54
- }
55
-
56
- .brand-badge {
57
- border-radius: 999px;
58
- padding: 6px 10px;
59
- background: var(--accent-soft);
60
- color: var(--accent);
61
- font-size: 12px;
62
- font-weight: 700;
63
- letter-spacing: 0.08em;
64
- text-transform: uppercase;
65
- }
66
-
67
- .brand-title {
68
- margin: 0;
69
- font-size: 1.1rem;
70
- }
71
-
72
- .nav {
73
- display: flex;
74
- flex-wrap: wrap;
75
- gap: 10px;
76
- }
77
-
78
- .nav-link,
79
- .button-link,
80
- .link {
81
- text-decoration: none;
82
- font-weight: 700;
83
- }
84
-
85
- .nav-link,
86
- .button-link {
87
- border-radius: 999px;
88
- border: 1px solid var(--line);
89
- background: rgba(255, 255, 255, 0.72);
90
- padding: 10px 14px;
91
- }
92
-
93
- .button-link.primary,
94
- .counter-button.primary {
95
- background: var(--accent);
96
- border-color: var(--accent);
97
- color: white;
98
- }
99
-
100
- .content {
101
- display: grid;
102
- gap: 24px;
103
- }
104
-
105
- .panel {
106
- border: 1px solid var(--line);
107
- border-radius: 26px;
108
- background: var(--panel);
109
- padding: 24px;
110
- box-shadow: var(--shadow);
111
- backdrop-filter: blur(12px);
112
- }
113
-
114
- .hero {
115
- padding: 32px;
116
- }
117
-
118
- .hero h1,
119
- .counter-panel h1 {
120
- margin: 0 0 12px;
121
- font-size: clamp(2.7rem, 7vw, 5rem);
122
- line-height: 0.94;
123
- letter-spacing: -0.045em;
124
- }
125
-
126
- .eyebrow {
127
- display: inline-block;
128
- margin-bottom: 12px;
129
- color: var(--accent);
130
- font-size: 0.9rem;
131
- font-weight: 700;
132
- letter-spacing: 0.08em;
133
- text-transform: uppercase;
134
- }
135
-
136
- .panel p,
137
- .list {
138
- color: var(--muted);
139
- font-size: 1.04rem;
140
- line-height: 1.7;
141
- }
142
-
143
- .hero-actions,
144
- .counter-actions {
145
- display: flex;
146
- flex-wrap: wrap;
147
- gap: 12px;
148
- margin-top: 24px;
149
- }
150
-
151
- .grid {
152
- display: grid;
153
- grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
154
- gap: 18px;
155
- }
156
-
157
- .counter-value {
158
- margin-top: 24px;
159
- font-size: clamp(4rem, 14vw, 7rem);
160
- line-height: 1;
161
- letter-spacing: -0.06em;
162
- }
163
-
164
- .counter-button {
165
- min-width: 88px;
166
- border-radius: 18px;
167
- border: 1px solid var(--line);
168
- background: white;
169
- color: var(--ink);
170
- padding: 14px 18px;
171
- font: inherit;
172
- font-weight: 700;
173
- cursor: pointer;
174
- }
175
-
176
- .counter-code {
177
- display: block;
178
- margin-top: 20px;
179
- color: var(--accent);
180
- white-space: pre-wrap;
181
- }
182
-
183
- .list {
184
- margin: 0;
185
- padding-left: 18px;
186
- }
187
-
188
- @media (max-width: 720px) {
189
- .shell {
190
- width: min(100vw - 20px, 1120px);
191
- padding-top: 16px;
192
- }
193
-
194
- .topbar {
195
- align-items: flex-start;
196
- flex-direction: column;
197
- }
198
-
199
- .hero,
200
- .panel {
201
- padding: 20px;
202
- }
203
- }
204
- `;
@@ -1,3 +0,0 @@
1
- import { worker } from "./runtime";
2
-
3
- export default worker;
@@ -1,30 +0,0 @@
1
- {
2
- "type": "application",
3
- "source-directories": [
4
- ".elm-ssr",
5
- "src",
6
- ".elm-ssr/src"
7
- ],
8
- "elm-version": "0.19.1",
9
- "dependencies": {
10
- "direct": {
11
- "elm/browser": "1.0.2",
12
- "elm/core": "1.0.5",
13
- "elm/html": "1.0.0",
14
- "elm/http": "2.0.0",
15
- "elm/json": "1.1.3",
16
- "elm/svg": "1.0.1",
17
- "elm/time": "1.0.0",
18
- "elm/url": "1.0.0"
19
- },
20
- "indirect": {
21
- "elm/bytes": "1.0.8",
22
- "elm/file": "1.0.5",
23
- "elm/virtual-dom": "1.0.3"
24
- }
25
- },
26
- "test-dependencies": {
27
- "direct": {},
28
- "indirect": {}
29
- }
30
- }
@@ -1,10 +0,0 @@
1
- {
2
- "name": "@elm-ssr/example-crypto-dashboard",
3
- "version": "0.0.0",
4
- "private": true,
5
- "type": "module",
6
- "description": "Crypto dashboard example: Tailwind + elm/svg + elm/http islands with 15s live refresh and cross-island event bus.",
7
- "dependencies": {
8
- "elm-ssr": "workspace:*"
9
- }
10
- }