diff --git a/landing-e2e/playwright.config.mts b/landing-e2e/playwright.config.mts
index 779181c..14f4f3b 100644
--- a/landing-e2e/playwright.config.mts
+++ b/landing-e2e/playwright.config.mts
@@ -12,6 +12,19 @@ const here = dirname(fileURLToPath(import.meta.url));
const workspaceRoot = resolve(here, '..');
const baseURL = process.env['BASE_URL'] || 'http://localhost:4200';
+/**
+ * The dev server is started on whatever port `BASE_URL` names, rather than
+ * always 4200.
+ *
+ * `reuseExistingServer` means Playwright will happily adopt whatever is already
+ * listening there — and if that is a different project's dev server, every test
+ * fails with "element(s) not found" while the real cause is that the page under
+ * test was never served. Set BASE_URL to move both halves at once:
+ *
+ * BASE_URL=http://localhost:4280 npx nx e2e landing-e2e
+ */
+const port = new URL(baseURL).port || '4200';
+
export default defineConfig({
testDir: './src',
outputDir: resolve(workspaceRoot, 'dist/.playwright/landing-e2e/test-output'),
@@ -25,7 +38,7 @@ export default defineConfig({
trace: 'on-first-retry',
},
webServer: {
- command: 'npx nx run landing:serve',
+ command: `npx nx run landing:serve --port=${port}`,
url: baseURL,
reuseExistingServer: true,
timeout: 180_000,
diff --git a/landing/src/app/core/copy.ts b/landing/src/app/core/copy.ts
index e6f12f8..05efe15 100644
--- a/landing/src/app/core/copy.ts
+++ b/landing/src/app/core/copy.ts
@@ -78,6 +78,7 @@ export interface Copy {
email: string;
organisation: string;
hours: string;
+ hoursHint: string;
message: string;
messageHint: string;
consent: string;
@@ -167,6 +168,7 @@ const es: Copy = {
email: 'Correo',
organisation: 'Organización',
hours: 'Horas por semana',
+ hoursHint: 'Por ejemplo, 4',
message: 'Mensaje',
messageHint: 'Mínimo 20 caracteres.',
consent: 'Acepto que guardéis mis datos para responderme.',
@@ -264,6 +266,7 @@ const en: Copy = {
email: 'E-mail',
organisation: 'Organisation',
hours: 'Hours per week',
+ hoursHint: 'For example, 4',
message: 'Message',
messageHint: 'At least 20 characters.',
consent: 'I agree to have my details stored so you can reply.',
@@ -361,6 +364,7 @@ const ca: Copy = {
email: 'Correu',
organisation: 'Organització',
hours: 'Hores per setmana',
+ hoursHint: 'Per exemple, 4',
message: 'Missatge',
messageHint: 'Mínim 20 caràcters.',
consent: 'Accepto que deseu les meves dades per respondre’m.',
diff --git a/landing/src/app/ui/contact-form.scss b/landing/src/app/ui/contact-form.scss
index a618085..35072e4 100644
--- a/landing/src/app/ui/contact-form.scss
+++ b/landing/src/app/ui/contact-form.scss
@@ -28,3 +28,28 @@
margin: 0;
color: var(--pi-color-warn, #d64545);
}
+
+// The hours field sits inside an @if, and that is what breaks its label.
+//
+// Angular Material measures an outlined field's floating label once, on init,
+// and writes the result to the element as an inline
+// `transform: var(--mat-mdc-form-field-label-transform, translateY(-50%) …)`.
+// A field created inside a conditional block is measured before it has any
+// geometry, so the value it keeps is the unfloated one: the label ends up 5px
+// above the wrapper instead of 18, straddling the 2px outline and struck
+// through by it.
+//
+// `@pi/ui` already publishes the right geometry for every other field
+// (`translateY(-3rem) scale(1)`, a 48px label box), and that rule matches this
+// one too — it simply loses to the inline style. `!important` is what beats an
+// inline declaration, and it is restating the library's own values, not
+// inventing new ones.
+:host ::ng-deep .pi-field.pi-form-field__number {
+ .mdc-notched-outline .mdc-floating-label {
+ height: var(--pi-spacing-xxxx-large) !important;
+ }
+
+ .mdc-text-field:not(.mat-form-field-no-animations) .mdc-floating-label {
+ transform: translateY(-3rem) scale(1) !important;
+ }
+}
diff --git a/landing/src/app/ui/contact-form.ts b/landing/src/app/ui/contact-form.ts
index 03c929c..1692f59 100644
--- a/landing/src/app/ui/contact-form.ts
+++ b/landing/src/app/ui/contact-form.ts
@@ -115,7 +115,17 @@ type Kind = 'contribute' | 'request';
{{ copy().form.organisation }}
} @else {
-
+
+
{{ copy().form.hours }}
}