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>