New treatment-room photos for the four-image strip in the massage
About-us block, cropped square at 800x800 to match the existing set
(the block is flex, and a non-square crop leaves the strip visibly
short against the bio) and stripped of EXIF.
Each is the full 4284px frame width, with the vertical offset picked
to hold both Vincent's face and the hands-on-client action. In pics 1
and 4 the subject is taller than the frame is wide, so the crop
sacrifices the top of his hair rather than the treatment itself.
Also corrects the strip's vertical alignment. The two columns were
already align-items: flex-start and did start level; the offset came
from the text, which is 13pt/1.75em and therefore carries 6.5px of
half-leading above its first line, while an inline image starts flush
at its box top. padding-top: 0.375em is exactly that half-leading. It
is zeroed at the 480px breakpoint, where the container turns
display:block and the strip no longer sits beside the text.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014wwCBWRnEZuorhv34FVUkY
Four items from the salon, all three languages where applicable.
Massage section icon
- The icon above Om oss (and above Behandlinger, Priser and Åpningstider -
it is the same image in all four) was beauty's makeup icon, copied in as
a placeholder before massage had any assets of its own. Replaced with the
herbal-compress icon tinted to the massage sage, mirroring how beauty's
is tinted to its rose. beauty/ollo_rose.png is untouched; the copy under
assets/img/massage/ is removed, and the one dead template rule that still
pointed at it (.box-heading:before, which renders on no page) repointed.
Parking copy, barber + beauty + massage, no/en/hu
- Dropped the "park free of charge during the treatments" clause from the
owner bio and replaced it with the new two-space wording.
- Replaced the note under Åpningstider. The Norwegian original was
misspelled differently in each vertical ("kundeprarking" on barber,
"kunderparking" on beauty); both are gone.
- massage picks both up automatically: its owner block is extracted from
beauty-form-<lang>.php at generation time, and the hours note comes from
the generator.
Barber treatments grid: level the six icons
- .service-text reserves padding-bottom for the 80px floated icon but has
no height, so every block sizes to its own text. Measured on the live
page the left column ran 155/155px against the right column's 185/206px,
because its titles wrap to two lines - so the icons drifted further apart
with each row (0, 30, 81px).
- Trimmed the reserved padding and gave every block the same floor, so all
six are identical and the rows line up. 195px clears the tallest block
and leaves room for a three-line title at the narrow end of the desktop
range. Desktop only: below 981px the columns stack full width, where a
floor would only add dead space.
- beauty drifts 20px and massage 0px, so both are left alone.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Introduces /massage alongside barber and beauty: landing page, booking
flow, admin support, home tile and SEO entries, in all three languages.
Architecture
- application/config/verticals.php + vertical_helper.php: one registry
entry per vertical (branding, assets, views, behaviour flags). A fourth
vertical is a config entry plus content files.
- Strangler: barber and beauty keep pointing at their existing view files,
so their rendered HTML is unchanged. Only massage uses the new generic
pages/vertical-*.php and includes/vertical-*.php views, which collapse
the four duplicated per-language nav/footer branches into one.
- Pages::vertical() + one route; booking(), booking_finished(),
_booking_error() and manage_booking() are now registry-driven.
Worker/vertical coupling
- getActiveWorkers() derives the vertical from services.service_category_id
instead of the workers.is_barber / is_beauty flags, which were a
hand-maintained cache of exactly that fact. Verified against production
data: the derived set reproduced the stored flags for every worker, in
both verticals. No schema change was needed for massage.
- The legacy flags are now written through from the category so a rollback
cannot strand a new worker, and the admin worker UI shows the derived
verticals read-only instead of two dropdowns that controlled nothing.
Bug fixes found along the way (all pre-existing)
- booking_process() had no server-side category guard; cross-vertical
mixing was prevented only by client-side JS.
- add-service-form / add-worker-form emitted `selected` on every category
option, so the newest category silently became the default.
- update-service-form offered only barber/beauty, so editing a service of
any other type silently rewrote it.
- getWorkerScheduleByDay ignored schedule overrides while getAvailableTimes
honoured them, so slots could be shown and then rejected. Added an
override-aware getWorkerScheduleForDate() and used it in both guards.
- Booking lists dereferenced a null service if one had been hard-deleted.
- main.css: .tiles was tuned for exactly two tiles, including an
absolutely-positioned .style1 at the 1280px breakpoint.
Massage-specific behaviour, opt-in per vertical
- strip_category_prefix: grouped service lists show "50 min" under the
treatment heading rather than repeating the full name. The full name is
carried in data-service-name so the totals panel stays unambiguous, and
services.service_name is untouched for emails and admin.
- single_service_booking: one treatment per booking, enforced in the UI and
in booking_process(). Re-clicking the selection releases it.
- Displayed treatment time (50/80/110 min) is in the service name; the
booked slot (60/90/120 min) is service_time and covers changing and
payment. service_time is never shown to the guest.
DB migrations for dev/prod are in documents/ - additive only, no ALTER.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
- New /ledigestilling/ index listing all open positions (split
barber/beauty card styling)
- Three ads: negletekniker + massør (beauty design), barber (gold
barber design with recolored CSS + assets)
- Rename ledigestilling.html -> negletekniker.html with 301 redirect
- "Ledige stillinger" footer link on barber/beauty pages (NO/EN/HU)
- Back-to-list link on each ad page
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>