diff --git a/src/editor/auth/LoginScreen.ts b/src/editor/auth/LoginScreen.ts
index f90f5a7b..51158df2 100644
--- a/src/editor/auth/LoginScreen.ts
+++ b/src/editor/auth/LoginScreen.ts
@@ -1,24 +1,73 @@
import { Graphics } from 'pixi.js';
+import logoSvg from '../../../public/brand/feuerwerk-logo-stacked-dark.svg?raw';
import { type AuthStore, type SignedIn, WrongCredentialsError } from '../../core/store/AuthStore';
-import { Button, drawControlBox, Label, Spacer, TextField, Theme, ui, VStack } from '../../ui';
+import {
+ Button,
+ drawFloatingBox,
+ drawSvg,
+ Label,
+ Spacer,
+ TextField,
+ Theme,
+ ui,
+ VStack,
+ Widget,
+} from '../../ui';
import { FormStatus, refocus } from './FormStatus';
import { loginError, loginProblem } from './loginRules';
-const CARD_WIDTH = 280;
+const CARD_WIDTH = 300;
+const CARD_PADDING = Theme.padding * 3;
+const FIELD_WIDTH = CARD_WIDTH - CARD_PADDING * 2;
-/** The box in the middle of the screen; the screen around it is the plain editor background. */
+/** The three colours of the logo's rays, outside in: corners, spokes, diagonals. */
+const BRAND_COLORS = [0x3fb6a8, 0xf5b83d, 0xf0782f] as const;
+
+/** Where the stacked logo draws in its own units: its `viewBox`. */
+const LOGO_BOX = { x: -62.876, y: 0, w: 247.962, h: 175.806 };
+const LOGO_SCALE = 0.8;
+
+/** The logo with the name below it (`public/brand/`), drawn as vectors so it stays sharp at any zoom. */
+class Logo extends Widget {
+ private readonly picture = new Graphics();
+
+ constructor() {
+ super();
+ this.label = 'login-logo';
+ this.eventMode = 'none';
+ this.minWidth = Math.round(LOGO_BOX.w * LOGO_SCALE);
+ this.minHeight = Math.round(LOGO_BOX.h * LOGO_SCALE);
+ // The file's `
` is for screen readers; Pixi's parser draws nothing for it but warns.
+ drawSvg(this.picture, logoSvg.replace(/[^<]*<\/title>/, ''), Theme.colors.text);
+ this.picture.scale.set(LOGO_SCALE);
+ this.addChild(this.picture);
+ }
+
+ override layout(): void {
+ const x = (this.w - LOGO_BOX.w * LOGO_SCALE) / 2 - LOGO_BOX.x * LOGO_SCALE;
+ const y = (this.h - LOGO_BOX.h * LOGO_SCALE) / 2 - LOGO_BOX.y * LOGO_SCALE;
+ this.picture.position.set(Math.round(x), Math.round(y));
+ }
+}
+
+/** The box under the logo: a floating panel with a strip in the logo's colours along its top. */
class Card extends VStack {
private readonly bg = new Graphics();
constructor() {
- super({ padding: Theme.padding * 2, gap: Theme.gap });
+ super({ padding: CARD_PADDING, gap: Theme.gap });
this.addChildAt(this.bg, 0);
this.minWidth = CARD_WIDTH;
}
override redraw(): void {
- this.bg.clear();
- drawControlBox(this.bg, this.w, this.h, { fill: Theme.colors.panel, border: Theme.colors.border });
+ drawFloatingBox(this.bg, this.w, this.h, Theme.colors.panel);
+ const strip = 3;
+ const third = this.w / BRAND_COLORS.length;
+ BRAND_COLORS.forEach((color, i) => {
+ this.bg.rect(Math.round(i * third), 0, Math.round((i + 1) * third) - Math.round(i * third), strip);
+ this.bg.fill(color);
+ });
}
}
@@ -30,6 +79,7 @@ class Card extends VStack {
* signed in, so there is nothing behind it to hide.
*/
export class LoginScreen extends VStack {
+ private readonly logo = new Logo();
private readonly user: TextField;
private readonly password: TextField;
private readonly submit: Button;
@@ -44,23 +94,25 @@ export class LoginScreen extends VStack {
) {
super({ align: 'center', gap: Theme.gap });
this.add(new Spacer(), 1);
+ this.add(this.logo);
+ this.add(new Spacer(Theme.padding * 3));
const card = new Card();
- card.add(new Label('Feuerwerk', { style: 'title' }));
- card.add(new Label('Sign in to open your projects', { style: 'muted' }));
- card.add(new Spacer(Theme.gap));
+ card.add(new Label('Sign in to open your projects', { style: 'bodyBold' }));
+ card.add(new Spacer(Theme.padding));
card.add(new Label('User name', { style: 'small' }));
this.user = new TextField('', {
- width: CARD_WIDTH - Theme.padding * 4,
+ width: FIELD_WIDTH,
placeholder: 'User name',
onSubmit: () => ui().focus.focus(this.password),
});
card.add(this.user);
+ card.add(new Spacer(Theme.gap));
card.add(new Label('Password', { style: 'small' }));
this.password = new TextField('', {
- width: CARD_WIDTH - Theme.padding * 4,
+ width: FIELD_WIDTH,
placeholder: 'Password',
secret: true,
onSubmit: () => void this.signIn(),
@@ -70,13 +122,14 @@ export class LoginScreen extends VStack {
card.add(this.status.message);
this.submit = new Button('Sign in', {
variant: 'primary',
- width: CARD_WIDTH - Theme.padding * 4,
+ width: FIELD_WIDTH,
onClick: () => void this.signIn(),
});
card.add(this.submit);
this.add(card);
- this.add(new Spacer(), 1);
+ // Twice the space below as above, so logo and card sit a little above the middle.
+ this.add(new Spacer(), 2);
}
/** Puts the caret in the first field, so a keyboard alone can sign in. */