Moving from the landing to a page read as changing sites: a narrow
centred column on a flat background, light headings, no cards. The
page now opens like the landing's hero — the same two lights, the same
left edge as the logo, a bold title with the excerpt as its lead — and
the body's headings are bold like every section title on the landing.
The lights move into a shared mixin (ui/_lights.scss) so the hero and
the pages cannot drift apart. On a page they sit on the whole <main>
with `overflow: clip`, so they fade out on their own instead of ending
in a hard edge right where the text starts, and `clip` rather than
`hidden` keeps the sticky side column working.
A side column keeps a page from being a dead end: "keep reading" cards
for the other pages, its own category first, and a card to get in
touch, both on the landing's card surface.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The landing was one address. It now also answers on the pages the panel
publishes -- "Sobre nosotros" and the like -- at the addresses the API
itself chooses: `path` and `alternates` put the default language
unprefixed and every other one under its code, so `/sobre-nosotros`,
`/en/about-us`, `/ca/qui-som` are the routes. `core/api.ts#contentRoute`
is the single rule for that shape, and `/es/…` is deliberately not one
of them, so a page has one address rather than two.
Two routes share one load function, because Analog wants a `.server.ts`
beside each `.page.ts`: `[slug]` for the default language, and
`[locale]/[slug]` for the rest.
Whether a slug exists is the API's answer, not something the route table
knows, so the 404 middleware lets page-shaped addresses through and
`ui/content-page.ts` sets the status itself from `ngOnInit` -- the same
`injectResponse()` the landing uses for its 503, and `ngOnInit` because
an input has no value in the constructor. Body HTML comes from the
panel's editor and goes through Angular's sanitiser.
The header shows the featured pages beside the section anchors; the
footer lists them all. On a page the language switcher follows the API's
`alternates`, and a language with no translation leads to the landing in
that language instead of a 404.
Verified: lint, 20 unit tests, build, and 6 e2e -- including a new one
that a page-shaped address the API does not know answers 404 with
`robots: noindex`.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Drops "Hecho con Angular, Analog y Nx." and the API link beside it,
leaving the copyright line on its own.
`footer.built` goes with it in all three locales. So does `footer.api`,
which was already dead -- declared and translated, never rendered.
The row's flex and space-between existed only to push the credit to the
far end, so they go too.
Verified in es/en/ca: no trace of the credit or the docs link in the
served HTML, and the copyright line renders in each.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The doubled edge was not the textarea's alone -- it was on every field,
and the previous commit's explanation of why the inputs were exempt was
wrong. I asserted that at 999px the ring and the outline clamp to the
same curve without measuring it. They do not:
ring 999px -> a 24px circle (half the field's height)
outline 999px -> a 12x24 ellipse (leading piece width x half height)
The ring is one continuous rounded rectangle over the whole field; the
outline beneath it is split into three pieces and its leading piece is
12px wide. No radius makes a full-width curve agree with a 12px-wide
one, so there was nothing to tune -- the ring had to go. It was mine,
lifted from the library's `gradient` theme onto a shell that does not
use it, and `alternative` already expresses focus through the outline's
own colour.
The textarea's radius goes back to 1.25rem, since it was only lowered to
dodge the clamp.
Verified at 3x, one curve per field: valid+focus violet, error+focus
red, no ring anywhere.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Focusing the textarea while it had an error drew two red curves down its
left side, a couple of pixels apart. Not a positioning problem -- the
ring and the outline share the same box to the pixel. It is clamping.
The outline's leading piece is 12px wide, so the 20px corner radius it
was given got clamped to 12, while the focus ring, drawn across the full
width, kept a true 20px curve. Same declared radius, two different
curves. At 10px nothing is clamped and they land on top of each other.
The single-line fields never showed this: at 999px both the outline and
the ring clamp to half the field's height, so they already agreed.
Verified at 2x: one continuous outline, ring and leading piece both
reporting 10px.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Focusing a field that already had an error painted two rings at once:
the notched outline stayed in the error colour and the gradient focus
ring went on top of it in the brand's violet-to-cyan. Two rings, two
colours, one field -- and on the textarea at two different radii, which
is what made it visible enough to report.
The ring itself is worth keeping; it is how focus reads everywhere else.
It just has to agree with the state it decorates, so while the field is
invalid it is built from the warn colour. The result is one red
indication that brightens on focus, and valid fields keep the brand
gradient.
Verified in both themes: an errored focused field's ring and outline are
both the warn colour, a valid focused one still gets the brand gradient.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
On the deployed site "Falta el nombre" sat 0px below its own field and
6px above the *next* field's label, so the three lines read as one block
and the message looked like a caption for "HORAS POR SEMANA".
Material renders a field's message flush underneath it, so the space
between rows has to be visibly larger than the space inside one. The
form's row gap goes from 1.25rem to 2rem: 0px to its own field, 18px to
the next label.
Measured against https://pireon.dev before and after. The earlier fixes
in this series are already live there and verified correct on the
deployed build -- labels uppercase at weight 700, the checkbox message
red at 12px on its own line, the textarea's ring at 20px, and no growth
on submit. This one needs a redeploy to show.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Pressing Enviar on an incomplete form pushed everything below each
errored field downwards. Material's subscript strip is 10px tall while a
field is valid and 24px once it carries a message, so the form grew by
14px per field at the moment of the click -- the submit button moved out
from under the cursor. The taller height is now reserved up front.
The consent message had two problems of its own. It rendered as an
inline span straight after the label, so it read as part of the
sentence: "Acepto que guardéis mis datos para responderme. Hace falta tu
consentimiento". And nothing in @pi/ui styles `.pi-checkbox__error`, so
it inherited the body colour and size -- ordinary text sitting beside
four red ones. It now gets its own reserved line, in the warn colour at
the same 12px as every other message.
Verified in both themes: the form grows 0px on submit, and all five
messages match at 12px in the warn colour.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Focusing a field visibly reflowed its label: "MENSAJE *" turned into
"Mensaje *" in a lighter face under the cursor. @pi/ui applies the
theme's label typography under `:not(.mdc-text-field--focused)`, so
text-transform, letter-spacing and weight all fall away the moment a
field takes focus. Restated without that guard. Colour is left alone --
Material tints the label on focus and that part is wanted.
The textarea was also drawn twice, once in the error colour and once in
the gradient, the two outlines offset from each other. Both take their
radius from `--pi-theme-field-radius`, which the `alternative` shell
sets to 999px: right for a 48px input, a stadium on a 112px textarea. A
fixed radius makes the two shapes coincide.
Both only appear in states the previous fix did not check -- it verified
the resting form only. Measured now in error+focus: all four labels
uppercase at weight 700, and the textarea's ring and outline both at
20px.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The label read "Horas por semana" struck through by the field's own
border: 5px above the wrapper instead of 18, straddling the 2px outline.
Angular Material measures an outlined field's floating label once on
init and writes the result to the element as an inline transform. This
field is the only one inside an @if, so it is measured before it has any
geometry and keeps the unfloated offset. @pi/ui already publishes the
right values for every other field and its rule matches this one too --
it simply loses to an inline style, so the fix restates the library's
own numbers with `!important`, which is what outranks one.
It also gains a placeholder, which every other field already had.
The e2e config now derives the dev server's port from BASE_URL instead
of pinning 4200. With `reuseExistingServer`, an unrelated project's dev
server on that port gets adopted silently and all five tests fail with
"element(s) not found" while the page under test was never served --
which is exactly what happened while verifying this.
Verified: all four labels now land at -18 with a 48px box, light and
dark; lint, 15 unit tests, build and 5 e2e green.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>