Greet with the Feuerwerk logo on the sign-in screen
feuerwerk / gate (push) Successful in 5m8s
feuerwerk / suite (push) Successful in 18m38s

The sign-in screen was a plain box with the name as a title. It now
shows the brand, so the first thing anyone sees is Feuerwerk itself.

- The stacked logo (name below the icon) sits above the card. It is
  drawn as vectors straight from public/brand/, so it stays sharp at
  any zoom. Its <title> is stripped first, Pixi warns about it.
- The card is a floating panel, 300 px wide, with more padding and a
  thin strip in the three colours of the logo along its top.
- The small "Feuerwerk" title in the card is gone, the logo says it.
- Logo and card sit a little above the middle of the screen.
- Labels, field names and error texts are unchanged, the tests look
  for them.

Tests:
- Gate on melektaus-atx: tsc, eslint, Prettier clean, 307 files,
  4762 tests green
- e2e 45 (login): ALL PASSED, 19 checks, run while the screen still
  had a glow behind the logo. The glow was taken out by hand after
  that, e2e 45 did not run again.
- Looked at in Chromium at 1280x800, empty and with a wrong password

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
adminandClaude Opus 5.5 committed 2026-10-13 15:59:35 +02:00
1 parent d4ff652293
commit ba68167704
1 file changed
+66 -13
+66 -13
View File
@@ -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 `<title>` is for screen readers; Pixi's parser draws nothing for it but warns.
drawSvg(this.picture, logoSvg.replace(/<title>[^<]*<\/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. */