@mideind/netskrafl-react 1.0.0-beta.1

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 (68) hide show
  1. package/.eslintignore +8 -0
  2. package/.eslintrc.json +13 -0
  3. package/README.md +63 -0
  4. package/dist/cjs/index.css +6837 -0
  5. package/dist/cjs/index.js +3046 -0
  6. package/dist/cjs/index.js.map +1 -0
  7. package/dist/esm/index.css +6837 -0
  8. package/dist/esm/index.js +3046 -0
  9. package/dist/esm/index.js.map +1 -0
  10. package/dist/types.d.ts +41 -0
  11. package/package.json +63 -0
  12. package/rollup.config.js +60 -0
  13. package/src/components/index.ts +2 -0
  14. package/src/components/netskrafl/Netskrafl.stories.tsx +66 -0
  15. package/src/components/netskrafl/Netskrafl.tsx +135 -0
  16. package/src/components/netskrafl/Netskrafl.types.ts +7 -0
  17. package/src/components/netskrafl/index.ts +2 -0
  18. package/src/css/fonts.css +4 -0
  19. package/src/css/glyphs.css +224 -0
  20. package/src/css/skrafl-explo.css +6616 -0
  21. package/src/fonts/glyphicons-regular.eot +0 -0
  22. package/src/fonts/glyphicons-regular.ttf +0 -0
  23. package/src/fonts/glyphicons-regular.woff +0 -0
  24. package/src/index.ts +2 -0
  25. package/src/messages/messages.json +1576 -0
  26. package/src/mithril/actions.ts +319 -0
  27. package/src/mithril/bag.ts +65 -0
  28. package/src/mithril/bestdisplay.ts +74 -0
  29. package/src/mithril/blankdialog.ts +94 -0
  30. package/src/mithril/board.ts +336 -0
  31. package/src/mithril/buttons.ts +303 -0
  32. package/src/mithril/challengedialog.ts +186 -0
  33. package/src/mithril/channel.ts +162 -0
  34. package/src/mithril/chat.ts +228 -0
  35. package/src/mithril/components.ts +496 -0
  36. package/src/mithril/dragdrop.ts +219 -0
  37. package/src/mithril/elopage.ts +180 -0
  38. package/src/mithril/friend.ts +227 -0
  39. package/src/mithril/game.ts +1378 -0
  40. package/src/mithril/gameview.ts +111 -0
  41. package/src/mithril/globalstate.ts +33 -0
  42. package/src/mithril/i18n.ts +186 -0
  43. package/src/mithril/localstorage.ts +133 -0
  44. package/src/mithril/login.ts +122 -0
  45. package/src/mithril/logo.ts +270 -0
  46. package/src/mithril/main.ts +737 -0
  47. package/src/mithril/mithril.ts +29 -0
  48. package/src/mithril/model.ts +817 -0
  49. package/src/mithril/movelistitem.ts +226 -0
  50. package/src/mithril/page.ts +852 -0
  51. package/src/mithril/playername.ts +91 -0
  52. package/src/mithril/promodialog.ts +82 -0
  53. package/src/mithril/recentlist.ts +148 -0
  54. package/src/mithril/request.ts +52 -0
  55. package/src/mithril/review.ts +634 -0
  56. package/src/mithril/rightcolumn.ts +398 -0
  57. package/src/mithril/searchbutton.ts +118 -0
  58. package/src/mithril/statsdisplay.ts +109 -0
  59. package/src/mithril/tabs.ts +169 -0
  60. package/src/mithril/tile.ts +145 -0
  61. package/src/mithril/twoletter.ts +76 -0
  62. package/src/mithril/types.ts +379 -0
  63. package/src/mithril/userinfodialog.ts +171 -0
  64. package/src/mithril/util.ts +304 -0
  65. package/src/mithril/wait.ts +246 -0
  66. package/src/mithril/wordcheck.ts +102 -0
  67. package/tsconfig.json +28 -0
  68. package/vite.config.ts +12 -0
@@ -0,0 +1,111 @@
1
+ /*
2
+
3
+ Gameview.ts
4
+
5
+ Game view component
6
+
7
+ Copyright (C) 2025 Miðeind ehf.
8
+ Author: Vilhjalmur Thorsteinsson
9
+
10
+ The Creative Commons Attribution-NonCommercial 4.0
11
+ International Public License (CC-BY-NC 4.0) applies to this software.
12
+ For further information, see https://github.com/mideind/Netskrafl
13
+
14
+ */
15
+
16
+ import { IView } from "./types";
17
+ import { t, ts } from "./i18n";
18
+ import { glyph } from "./util";
19
+ import { ComponentFunc, m } from "./mithril";
20
+ import { Info, LeftLogo } from "./components";
21
+ import { BlankDialog } from "./blankdialog";
22
+ import { Bag } from "./bag";
23
+ import { BoardArea } from "./board";
24
+ import { RightColumn } from "./rightcolumn";
25
+
26
+ const Beginner: ComponentFunc<IAttributes> = (initialVnode) => {
27
+ // Show the board color guide
28
+ const view = initialVnode.attrs.view;
29
+ const model = view.model;
30
+ const state = model.state;
31
+ return {
32
+ view: () => m(".board-help",
33
+ { title: ts("Hvernig reitirnir margfalda stigin") },
34
+ [
35
+ m(".board-help-close",
36
+ {
37
+ title: ts("Loka þessari hjálp"),
38
+ onclick: (ev: Event) => {
39
+ // Close the guide and set a preference not to see it again
40
+ if (state) {
41
+ state.beginner = false;
42
+ model.setUserPref({ beginner: false });
43
+ }
44
+ ev.preventDefault();
45
+ }
46
+ },
47
+ glyph("remove")
48
+ ),
49
+ m(".board-colors",
50
+ [
51
+ m(".board-color[id='triple-word']", ["3 x", m("br"), t("orð")]),
52
+ m(".board-color[id='double-word']", ["2 x", m("br"), t("orð")]),
53
+ m(".board-color[id='triple-letter']", ["3 x", m("br"), t("stafur")]),
54
+ m(".board-color[id='double-letter']", ["2 x", m("br"), t("stafur")]),
55
+ m(".board-color[id='single-letter']", ["1 x", m("br"), t("stafur")])
56
+ ]
57
+ )
58
+ ]
59
+ )
60
+ };
61
+ };
62
+
63
+ interface IAttributes {
64
+ view: IView;
65
+ }
66
+
67
+ export const GameView: ComponentFunc<IAttributes> = () => {
68
+ // A view of a game, in-progress or finished
69
+
70
+ return {
71
+ view: (vnode) => {
72
+ const view = vnode.attrs.view;
73
+ const model = view.model;
74
+ const game = model.game;
75
+ if (!game) {
76
+ // No associated game
77
+ return m("div", [m(".game-container"), m(LeftLogo)]);
78
+ }
79
+ const bag = game.bag;
80
+ const newbag = game.newbag;
81
+ const state = model.state;
82
+ return m(
83
+ // Allow tiles to be dropped on the background
84
+ // by marking the div with the drop-target class.
85
+ // Such a drop sends the tile back into the rack.
86
+ "div.drop-target",
87
+ {
88
+ id: "board-background",
89
+ },
90
+ [
91
+ // The main game area
92
+ m(".game-container",
93
+ [
94
+ m(RightColumn, { view }),
95
+ m(BoardArea, { view }),
96
+ // The bag is visible in fullscreen
97
+ state?.uiFullscreen ? m(Bag, { bag: bag, newbag: newbag }) : "",
98
+ game.askingForBlank ? m(BlankDialog, { view }) : ""
99
+ ]
100
+ ),
101
+ // The left margin stuff: back button, square color help, info/help button
102
+ // These elements appear after the game container, since we want
103
+ // them to be above it in the z-order
104
+ m(LeftLogo),
105
+ state?.beginner ? m(Beginner, { view }) : "",
106
+ m(Info),
107
+ ]
108
+ )
109
+ }
110
+ };
111
+ };
@@ -0,0 +1,33 @@
1
+
2
+ // Global state, for the most part obtained from the server
3
+ // when the single page UI is initialized (i.e. from page.html)
4
+ export interface GlobalState {
5
+ projectId: string;
6
+ databaseURL: string;
7
+ firebaseSenderId: string;
8
+ firebaseAppId: string;
9
+ firebaseAPIKey: string;
10
+ measurementId: string;
11
+ userEmail: string;
12
+ userId: string;
13
+ userNick: string;
14
+ userFullname: string;
15
+ locale: string;
16
+ isExplo: boolean;
17
+ serverUrl: string;
18
+ movesUrl: string;
19
+ movesAccessKey: string;
20
+ token: string;
21
+ loginUrl: string;
22
+ loginMethod: string;
23
+ newUser: boolean;
24
+ beginner: boolean;
25
+ fairPlay: boolean;
26
+ plan: string;
27
+ hasPaid: boolean;
28
+ ready: boolean;
29
+ readyTimed: boolean;
30
+ uiFullscreen: boolean;
31
+ uiLandscape: boolean;
32
+ runningLocal: boolean;
33
+ }
@@ -0,0 +1,186 @@
1
+ /*
2
+
3
+ i8n.ts
4
+
5
+ Single page UI for Explo using the Mithril library
6
+
7
+ Copyright (C) 2023 Miðeind ehf.
8
+ Author: Vilhjálmur Þorsteinsson
9
+
10
+ The Creative Commons Attribution-NonCommercial 4.0
11
+ International Public License (CC-BY-NC 4.0) applies to this software.
12
+ For further information, see https://github.com/mideind/Netskrafl
13
+
14
+
15
+ This module contains internationalization (i18n) utility functions,
16
+ allowing for translation of displayed text between languages.
17
+
18
+ Text messages for individual locales are loaded from the
19
+ /static/assets/messages.json file, which is fetched from the server.
20
+
21
+ */
22
+
23
+ export { t, ts, mt, loadMessages };
24
+
25
+ import { m, VnodeChildren } from "./mithril";
26
+ import { request } from "./request";
27
+
28
+ // Type declarations
29
+ type Messages = { [key: string]: { [locale: string]: string | string[] }};
30
+ type FlattenedMessages = { [key: string]: { [locale: string]: VnodeChildren }};
31
+ type Interpolations = { [key: string]: string };
32
+
33
+ // Current exact user locale and fallback locale ("en" for "en_US"/"en_GB"/...)
34
+ // This is overwritten in setLocale()
35
+ let currentLocale = "is_IS";
36
+ let currentFallback = "is";
37
+
38
+ // Regex that matches embedded interpolations such as "Welcome, {username}!"
39
+ // Interpolation identifiers should only contain ASCII characters, digits and '_'
40
+ const rex = /{\s*(\w+)\s*}/g;
41
+
42
+ let messages: FlattenedMessages = {};
43
+ let messagesLoaded = false;
44
+
45
+ function hasAnyTranslation(msgs: Messages, locale: string): boolean {
46
+ // Return true if any translation is available for the given locale
47
+ for (let key in msgs) {
48
+ if (msgs[key][locale] !== undefined)
49
+ return true;
50
+ }
51
+ return false;
52
+ }
53
+
54
+ function setLocale(locale: string, msgs: Messages): void {
55
+ // Set the current i18n locale and fallback
56
+ currentLocale = locale;
57
+ currentFallback = locale.split("_")[0];
58
+ // For unsupported locales, i.e. locales that have no
59
+ // translations available for them, fall back to English (U.S.).
60
+ if (!hasAnyTranslation(msgs, currentLocale) && !hasAnyTranslation(msgs, currentFallback)) {
61
+ currentLocale = "en_US";
62
+ currentFallback = "en";
63
+ }
64
+
65
+ // Flatten the Messages structure, enabling long strings
66
+ // to be represented as string arrays in the messages.json file
67
+ messages = {};
68
+ for (let key in msgs) {
69
+ for (let lc in msgs[key]) {
70
+ let s: VnodeChildren = msgs[key][lc];
71
+ if (Array.isArray(s))
72
+ s = s.join("");
73
+ if (messages[key] === undefined)
74
+ messages[key] = {};
75
+ // If the string s contains HTML markup of the form <tag>...</tag>,
76
+ // convert it into a list of Mithril Vnode children corresponding to
77
+ // the text and the tags
78
+ if (s.match(/<[a-z]+>/)) {
79
+ // Looks like the string contains HTML markup
80
+ const vnodes: VnodeChildren[] = [];
81
+ let i = 0;
82
+ let tagMatch: RegExpMatchArray | null = null;
83
+ while (i < s.length && (tagMatch = s.slice(i).match(/<[a-z]+>/)) && tagMatch.index !== undefined) {
84
+ // Found what looks like an HTML tag
85
+ // Calculate the index of the enclosed text within s
86
+ const tag = tagMatch[0];
87
+ let j = i + tagMatch.index + tag.length;
88
+ // Find the end tag
89
+ let end = s.indexOf("</" + tag.slice(1), j);
90
+ if (end < 0) {
91
+ // No end tag - skip past this weirdness
92
+ i = j;
93
+ continue;
94
+ }
95
+ // Add the text preceding the tag
96
+ if (tagMatch.index > 0)
97
+ vnodes.push(s.slice(i, i + tagMatch.index));
98
+ // Create the Mithril node corresponding to the tag and the enclosed text
99
+ // and add it to the list
100
+ vnodes.push(m(tag.slice(1, -1), s.slice(j, end)));
101
+ // Advance the index past the end of the tag
102
+ i = end + tag.length + 1;
103
+ }
104
+ // Push the final text part, if any
105
+ if (i < s.length)
106
+ vnodes.push(s.slice(i));
107
+ // Reassign s to the list of vnodes
108
+ s = vnodes;
109
+ }
110
+ messages[key][lc] = s;
111
+ }
112
+ }
113
+ messagesLoaded = true;
114
+ }
115
+
116
+ async function loadMessages(locale: string) {
117
+ // Load the internationalization message JSON file from the server
118
+ // and set the user's locale
119
+ try {
120
+ const messages = await request<Messages>({
121
+ method: "GET",
122
+ url: "/static/assets/messages.json",
123
+ });
124
+ setLocale(locale, messages);
125
+ }
126
+ catch {
127
+ setLocale(locale, {});
128
+ }
129
+ }
130
+
131
+ function t(key: string, ips: Interpolations = {}): VnodeChildren {
132
+ // Main text translation function, supporting interpolation
133
+ // and HTML tag substitution
134
+ const msgDict = messages[key];
135
+ if (msgDict === undefined)
136
+ // No dictionary for this key - may actually be a missing entry
137
+ return messagesLoaded ? key : "";
138
+ // Lookup exact locale, then fallback, then resort to returning the key
139
+ const message = msgDict[currentLocale] || msgDict[currentFallback] || key;
140
+ // If we have an interpolation object, do the interpolation first
141
+ return Object.keys(ips).length ? interpolate(message, ips) : message;
142
+ }
143
+
144
+ function ts(key: string, ips: Interpolations = {}): string {
145
+ // String translation function, supporting interpolation
146
+ // but not HTML tag substitution
147
+ const msgDict = messages[key];
148
+ if (msgDict === undefined)
149
+ // No dictionary for this key - may actually be a missing entry
150
+ return messagesLoaded ? key : "";
151
+ // Lookup exact locale, then fallback, then resort to returning the key
152
+ const message = msgDict[currentLocale] || msgDict[currentFallback] || key;
153
+ if (typeof message != "string")
154
+ // This is actually an error - the client should be calling t() instead
155
+ return "";
156
+ // If we have an interpolation object, do the interpolation first
157
+ return Object.keys(ips).length ? interpolate_string(message, ips) : message;
158
+ }
159
+
160
+ function mt(cls: string, children: VnodeChildren): VnodeChildren {
161
+ // Wrapper for the Mithril m() function that auto-translates
162
+ // string and array arguments
163
+ if (typeof children == "string") {
164
+ return m(cls, t(children));
165
+ }
166
+ if (Array.isArray(children)) {
167
+ return m(cls, children.map((item) => (typeof item == "string") ? t(item) : item));
168
+ }
169
+ return m(cls, children);
170
+ }
171
+
172
+ function interpolate(message: VnodeChildren, ips: Interpolations): VnodeChildren {
173
+ // Replace interpolation placeholders with their corresponding values
174
+ if (typeof message == "string") {
175
+ return message.replace(rex, (match, key) => ips[key] || match);
176
+ }
177
+ if (Array.isArray(message)) {
178
+ return message.map((item) => interpolate(item, ips));
179
+ }
180
+ return message;
181
+ }
182
+
183
+ function interpolate_string(message: string, ips: Interpolations): string {
184
+ // Replace interpolation placeholders with their corresponding values
185
+ return message.replace(rex, (match, key) => ips[key] || match);
186
+ }
@@ -0,0 +1,133 @@
1
+ /*
2
+
3
+ Localstorage.ts
4
+
5
+ An interface around HTML5 local storage functionality, if available
6
+
7
+ Copyright (C) 2024 Miðeind ehf.
8
+ Author: Vilhjálmur Þorsteinsson
9
+
10
+ The Creative Commons Attribution-NonCommercial 4.0
11
+ International Public License (CC-BY-NC 4.0) applies to this software.
12
+ For further information, see https://github.com/mideind/Netskrafl
13
+
14
+ */
15
+
16
+ import { RACK_SIZE, SavedTile } from "./types";
17
+
18
+ export interface LocalStorage {
19
+ getLocalTile: (ix: number) => string;
20
+ getLocalTileSq: (ix: number) => string;
21
+ setLocalTile: (ix: number, t: string) => void;
22
+ setLocalTileSq: (ix: number, sq: string) => void;
23
+ clearTiles: () => void;
24
+ saveTiles: (tilesPlaced: SavedTile[]) => void;
25
+ loadTiles: () => SavedTile[];
26
+ }
27
+
28
+ let _hasLocal: boolean | null = null; // Is HTML5 local storage supported by the browser?
29
+
30
+ function hasLocalStorage(): boolean {
31
+ // Return true if HTML5 local storage is supported by the browser
32
+ if (_hasLocal === null)
33
+ try {
34
+ _hasLocal = ('localStorage' in window) &&
35
+ (window.localStorage !== null) &&
36
+ (window.localStorage !== undefined);
37
+ } catch (e) {
38
+ _hasLocal = false;
39
+ }
40
+ return _hasLocal;
41
+ }
42
+
43
+ class LocalStorageImpl implements LocalStorage {
44
+
45
+ _prefix: string;
46
+
47
+ constructor(uuid: string) {
48
+ // Constructor for local storage associated with a particular game
49
+ this._prefix = `game.${uuid}`;
50
+ }
51
+
52
+ getLocalTile(ix: number) {
53
+ return window.localStorage[`${this._prefix}.tile.${ix}.t`];
54
+ }
55
+
56
+ getLocalTileSq(ix: number) {
57
+ return window.localStorage[`${this._prefix}.tile.${ix}.sq`];
58
+ }
59
+
60
+ setLocalTile(ix: number, t: string) {
61
+ window.localStorage[`${this._prefix}.tile.${ix}.t`] = t;
62
+ }
63
+
64
+ setLocalTileSq(ix: number, sq: string) {
65
+ window.localStorage[`${this._prefix}.tile.${ix}.sq`] = sq;
66
+ }
67
+
68
+ clearTiles() {
69
+ // Clean up local storage when game is over
70
+ try {
71
+ const ls = window.localStorage;
72
+ for (let i = 1; i <= RACK_SIZE; i++) {
73
+ ls.removeItem(`${this._prefix}.tile.${i}.sq`);
74
+ ls.removeItem(`${this._prefix}.tile.${i}.t`);
75
+ }
76
+ }
77
+ catch (e) {
78
+ }
79
+ }
80
+
81
+ saveTiles(tilesPlaced: SavedTile[]) {
82
+ // Save tile locations in local storage
83
+ let i: number;
84
+ for (i = 0; i < tilesPlaced.length; i++) {
85
+ // Store this placed tile in local storage
86
+ const sq = tilesPlaced[i].sq;
87
+ const tile = tilesPlaced[i].tile;
88
+ // Set the placed tile's square
89
+ this.setLocalTileSq(i + 1, sq);
90
+ // Set the letter (or ?+letter if undefined)
91
+ this.setLocalTile(i + 1, tile);
92
+ }
93
+ // Erase all remaining positions in local storage
94
+ for (; i < RACK_SIZE; i++) {
95
+ this.setLocalTileSq(i + 1, "");
96
+ this.setLocalTile(i + 1, "");
97
+ }
98
+ }
99
+
100
+ loadTiles() {
101
+ // Return the saved tile locations
102
+ let sq: string, tile: string;
103
+ let tp: SavedTile[] = [];
104
+ for (let i = 0; i < RACK_SIZE; i++) {
105
+ sq = this.getLocalTileSq(i + 1);
106
+ tile = this.getLocalTile(i + 1);
107
+ if (sq && tile)
108
+ tp.push({sq: sq, tile: tile});
109
+ }
110
+ return tp;
111
+ }
112
+
113
+ } // class LocalStorageImpl
114
+
115
+ class NoLocalStorageImpl implements LocalStorage {
116
+
117
+ // This class is used if the browser does not implement local storage
118
+
119
+ constructor() { }
120
+
121
+ getLocalTile(ix: number) { return ""; }
122
+ getLocalTileSq(ix: number) { return ""; }
123
+ setLocalTile(ix: number, t: string) { }
124
+ setLocalTileSq(ix: number, sq: string) { }
125
+ clearTiles() { }
126
+ saveTiles(tilesPlaced: SavedTile[]) { }
127
+ loadTiles(): SavedTile[] { return []; }
128
+
129
+ } // class NoLocalStorageImpl
130
+
131
+ export const getLocalStorage = (uuid: string): LocalStorage => {
132
+ return hasLocalStorage() ? new LocalStorageImpl(uuid) : new NoLocalStorageImpl();
133
+ }
@@ -0,0 +1,122 @@
1
+ /*
2
+
3
+ Login.ts
4
+
5
+ Login UI for Explo using the Mithril library
6
+
7
+ Copyright (C) 2025 Miðeind ehf.
8
+ Author: Vilhjálmur Þorsteinsson
9
+
10
+ The Creative Commons Attribution-NonCommercial 4.0
11
+ International Public License (CC-BY-NC 4.0) applies to this software.
12
+ For further information, see https://github.com/mideind/Netskrafl
13
+
14
+ This UI is built on top of Mithril (https://mithril.js.org), a lightweight,
15
+ straightforward JavaScript single-page reactive UI library.
16
+
17
+ */
18
+
19
+ import { m, ComponentFunc, Component } from "./mithril";
20
+
21
+ import { mt, t } from "./i18n";
22
+ import { AnimatedExploLogo, NetskraflLegend } from "./logo";
23
+ import { request } from "./request";
24
+
25
+ export interface LoginData {
26
+ status: string;
27
+ message?: string;
28
+ account: string;
29
+ user_id: string;
30
+ token: string;
31
+ firebase_token: string;
32
+ }
33
+
34
+ export const loginUserByEmail = async (
35
+ email: string,
36
+ nickname: string,
37
+ fullname: string,
38
+ token: string,
39
+ ) => {
40
+ // Call the /login_malstadur endpoint on the server
41
+ // to log in the user with the given email and token.
42
+ // The token is a standard HS256-encoded JWT with aud "netskrafl"
43
+ // and iss typically "malstadur".
44
+ return request<LoginData>({
45
+ method: "POST",
46
+ url: "/login_malstadur",
47
+ body: { email, nickname, fullname, token }
48
+ });
49
+ }
50
+
51
+ export const LoginError: Component<{ message: string}> = {
52
+ view: (vnode) => {
53
+ return m(
54
+ "div.error",
55
+ { style: { visibility: "visible" }},
56
+ vnode.attrs?.message || "Error logging in",
57
+ );
58
+ }
59
+ }
60
+
61
+ export const LoginForm: ComponentFunc<{ loginUrl: string }> = (initialVnode) => {
62
+
63
+ const loginUrl = initialVnode.attrs.loginUrl;
64
+ let loginInProgress = false;
65
+
66
+ function doLogin(ev: MouseEvent) {
67
+ loginInProgress = true;
68
+ ev.preventDefault();
69
+ window.location.href = loginUrl;
70
+ }
71
+
72
+ return {
73
+ view: () => {
74
+ return m.fragment({}, [
75
+ // This is visible on large screens
76
+ m("div.loginform-large", [
77
+ m(AnimatedExploLogo, {
78
+ className: "login-logo",
79
+ width: 200,
80
+ withCircle: false,
81
+ msStepTime: 150,
82
+ once: true
83
+ }),
84
+ m(NetskraflLegend, {
85
+ className: "login-legend",
86
+ width: 600,
87
+ msStepTime: 0
88
+ }),
89
+ mt("div.welcome", "welcome_0"),
90
+ mt("div.welcome", "welcome_1"),
91
+ mt("div.welcome", "welcome_2"),
92
+ m("div.login-btn-large",
93
+ { onclick: doLogin },
94
+ loginInProgress ? t("Skrái þig inn...") : [
95
+ t("Innskrá") + " ", m("span.glyphicon.glyphicon-play")
96
+ ]
97
+ )
98
+ ]),
99
+ // This is visible on small screens
100
+ m("div.loginform-small", [
101
+ m(AnimatedExploLogo, {
102
+ className: "login-logo",
103
+ width: 160,
104
+ withCircle: false,
105
+ msStepTime: 150,
106
+ once: true
107
+ }),
108
+ m(NetskraflLegend, {
109
+ className: "login-legend",
110
+ width: 650,
111
+ msStepTime: 0
112
+ }),
113
+ m("div.login-btn-small",
114
+ { onclick: doLogin },
115
+ loginInProgress ? t("Skrái þig inn...") : t("Innskrá")
116
+ )
117
+ ])
118
+ ]);
119
+ }
120
+ };
121
+
122
+ };