Add massage as a third vertical, driven by a config registry

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>
This commit is contained in:
Ubuntu
2026-08-13 16:34:47 +00:00
co-authored by Claude Opus 5
parent 0fd7f7a7a5
commit 5c30467fbd
61 changed files with 7130 additions and 169 deletions
@@ -43,7 +43,7 @@
<p>
I believe that beauty, fashion, and trend are not gender-specific, so we have the
opportunity for everyone to become beautiful in one place, regardless of gender.
And among our Barber and Beauty departments, you can find the right services
And among our Barber, Beauty and Massage departments, you can find the right services
for you.
</p>
<p>
@@ -34,7 +34,7 @@
A minőségi idő, a maximális figyelem, a legprofesszionálisabb eszközök, a hivatástudattal és szakértelemmel karöltve jelentik a filozófiánk és szolgáltatásaink alapjait.
</p>
<p>
Hiszem, hogy a szépség, a divat és a trend nem nemhez kötött, így nálunk lehetőség van arra, hogy mindenki egy helyen szépülhessen nemtől függetlenül. A Barber és Beauty részlegeink között pedig megtalálhatjátok a számotokra megfelelő szolgáltatásokat.
Hiszem, hogy a szépség, a divat és a trend nem nemhez kötött, így nálunk lehetőség van arra, hogy mindenki egy helyen szépülhessen nemtől függetlenül. A Barber, Beauty és Masszázs részlegeink között pedig megtalálhatjátok a számotokra megfelelő szolgáltatásokat.
</p>
<p>
Közel 10 éves szépségipari, és közte több éves nemzetközi tapasztalattal elérkezettnek láttam az időt, hogy megnyissam saját stúdiómat itt Trondheimben, az Innheredsveien 92 szám alatt, ahol a vendégeknek lehetősége van ingyenes parkolásra is a kezelések időtartama alatt.
@@ -35,7 +35,7 @@
Kvalitetstid, maksimal oppmerksomhet, de mest profesjonelle verktøyene, hånd i hånd med profesjonalitet og kompetanse, utgjør grunnlaget for vår filosofi og våre tjenester.
</p>
<p>
Jeg mener at skjønnhet, mote og trend ikke er kjønnsspesifikke, og hos oss har derfor alle mulighet til å bli vakrere på ett sted, uavhengig av kjønn. Hos Studio Beve Barber og Studio Beve Beauty kan dere finne de tjenestene som er riktige for dere.
Jeg mener at skjønnhet, mote og trend ikke er kjønnsspesifikke, og hos oss har derfor alle mulighet til å bli vakrere på ett sted, uavhengig av kjønn. Hos Studio Beve Barber, Studio Beve Beauty og Studio Beve Massasje kan dere finne de tjenestene som er riktige for dere.
</p>
<p>
Med nesten 10 års erfaring fra skjønnhetsindustrien, inkludert mange års internasjonal erfaring, tenkte jeg det var på tide å åpne mitt eget studio her i Trondheim i Innheredsveien 92, hvor gjestene har mulighet til å parkere gratis under behandlingene.
@@ -41,7 +41,7 @@
<p>
I believe that beauty, fashion, and trend are not gender-specific, so we have the
opportunity for everyone to become beautiful in one place, regardless of gender.
And among our Barber and Beauty departments, you can find the right services
And among our Barber, Beauty and Massage departments, you can find the right services
for you.
</p>
<p>
@@ -39,7 +39,7 @@
<p>
Hiszem, hogy a szépség, a divat és a trend nem nemhez kötött, így nálunk
lehetőség van arra, hogy mindenki egy helyen szépülhessen nemtől függetlenül.
A Barber és Beauty részlegeink között pedig megtalálhatjátok a számotokra
A Barber, Beauty és Masszázs részlegeink között pedig megtalálhatjátok a számotokra
megfelelő szolgáltatásokat.
</p>
<p>
@@ -40,7 +40,7 @@
<p>
Jeg mener at skjønnhet, mote og trend ikke er kjønnsspesifikke, og hos oss har
derfor alle mulighet til å bli vakrere på ett sted, uavhengig av kjønn. Hos Studio
Beve Barber og Studio Beve Beauty kan dere finne de tjenestene som er riktige
Beve Barber, Studio Beve Beauty og Studio Beve Massasje kan dere finne de tjenestene som er riktige
for dere.
</p>
<p>
@@ -6,6 +6,9 @@
'alternate_week' => 'The selected staff member only works every second week. Please choose another day.',
'after_hours' => 'The treatment would not finish before closing time. Please choose an earlier time.',
'too_far' => 'Bookings can only be made up to 3 months in advance.',
'category_mismatch' => 'The selected staff member does not perform the chosen services. Please start again.',
'single_service_only' => 'Only one treatment can be booked at a time. Please select just one.',
'no_service' => 'No service was selected. Please choose at least one service.',
'default' => 'Something went wrong with your booking. Please try again.',
);
@@ -6,6 +6,9 @@
'alternate_week' => 'A kiválasztott kolléga csak kéthetente dolgozik. Kérjük, válasszon másik napot.',
'after_hours' => 'A kezelés nem érne véget zárásig. Kérjük, válasszon korábbi időpontot.',
'too_far' => 'Foglalás legfeljebb 3 hónappal előre adható le.',
'category_mismatch' => 'A kiválasztott kolléga nem végzi a kiválasztott szolgáltatásokat. Kérjük, válasszon újra.',
'single_service_only' => 'Egyszerre csak egy kezelés foglalható. Kérjük, csak egyet válasszon.',
'no_service' => 'Nem választott ki szolgáltatást. Kérjük, válasszon legalább egyet.',
'default' => 'Hiba történt a foglalás során. Kérjük, próbálja újra.',
);
@@ -6,6 +6,9 @@
'alternate_week' => 'Den valgte medarbeideren jobber kun annenhver uke. Vennligst velg en annen dag.',
'after_hours' => 'Behandlingen rekker ikke å bli ferdig innen stengetid. Vennligst velg et tidligere tidspunkt.',
'too_far' => 'Du kan kun bestille time inntil 3 måneder frem i tid.',
'category_mismatch' => 'Den valgte medarbeideren utfører ikke de valgte tjenestene. Vennligst velg på nytt.',
'single_service_only' => 'Du kan kun bestille én behandling om gangen. Vennligst velg kun én.',
'no_service' => 'Du har ikke valgt noen tjeneste. Vennligst velg minst én tjeneste.',
'default' => 'Noe gikk galt med bestillingen. Vennligst prøv igjen.',
);
@@ -95,8 +95,8 @@
foreach ($groups[$label] as $serviceItem) {
?>
<div class="serviceRow">
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>">
<?php echo $serviceItem->service_name; ?>
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>" data-service-name="<?php echo htmlspecialchars($serviceItem->service_name, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo vertical_service_label(isset($vertical) ? $vertical : NULL, $serviceItem); ?>
</div>
<div class="serviceCol">
@@ -236,6 +236,26 @@
}
});
<?php if ( ! empty($vertical['single_service_booking'])): ?>
/* Single-service verticals (massage): a guest books one treatment at a
time, so selecting one clears and disables the rest. Clicking the
selected one again releases everything - the escape hatch for a
mis-click. Bound BEFORE the totals handler below, so that handler
always sees the corrected state. */
$('.service').click(function(){
if($(this).is(':checked')){
$('.service').not(this)
.prop('checked', false)
.prop('disabled', true)
.closest('.serviceRow').addClass('serviceRowDisabled');
}
else{
$('.service').prop('disabled', false);
$('.serviceRow').removeClass('serviceRowDisabled');
}
});
<?php endif; ?>
$('.service').click(function(){
var totalPrice = 0;
var selectedServices = '<div class="selectedServiceTable">';
@@ -249,7 +269,10 @@
var selected_id = $(this).attr('id');
selectedPriceElement = document.getElementById('servicePrice_'+selected_id).innerHTML;
totalPrice = totalPrice + parseInt(selectedPriceElement);
selectedNameElement = document.getElementById('serviceName_'+selected_id).innerHTML;
var nameNode = document.getElementById('serviceName_'+selected_id);
// Prefer the full name: the visible label may have had its
// category prefix stripped for readability.
selectedNameElement = nameNode.getAttribute('data-service-name') || nameNode.innerHTML;
selectedServices = selectedServices + '<div class="totalServiceRow"><div class="selectedServiceCol totalSelectedServiceName">'+selectedNameElement+'</div><div class="selectedServiceCol totalSelectedServicePrice">'+selectedPriceElement+' kr</div></div>';
var selectedTotalTime = $('#servicelength').val();
@@ -355,8 +378,13 @@
},
}).done(function(result){
$('#workerTable').html(result.workerListShow);
var firstWorker = result.workers;
setWorker(firstWorker[0].worker_id)
if(result.workers && result.workers.length > 0){
setWorker(result.workers[0].worker_id);
}
else{
$('#workerTable').html('<div class="noWorkerMessage">No staff available for this service.</div>');
setWorker('');
}
});
}
@@ -95,8 +95,8 @@
foreach ($groups[$label] as $serviceItem) {
?>
<div class="serviceRow">
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>">
<?php echo $serviceItem->service_name; ?>
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>" data-service-name="<?php echo htmlspecialchars($serviceItem->service_name, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo vertical_service_label(isset($vertical) ? $vertical : NULL, $serviceItem); ?>
</div>
<div class="serviceCol">
@@ -235,6 +235,26 @@
}
});
<?php if ( ! empty($vertical['single_service_booking'])): ?>
/* Single-service verticals (massage): a guest books one treatment at a
time, so selecting one clears and disables the rest. Clicking the
selected one again releases everything - the escape hatch for a
mis-click. Bound BEFORE the totals handler below, so that handler
always sees the corrected state. */
$('.service').click(function(){
if($(this).is(':checked')){
$('.service').not(this)
.prop('checked', false)
.prop('disabled', true)
.closest('.serviceRow').addClass('serviceRowDisabled');
}
else{
$('.service').prop('disabled', false);
$('.serviceRow').removeClass('serviceRowDisabled');
}
});
<?php endif; ?>
$('.service').click(function(){
var totalPrice = 0;
var selectedServices = '<div class="selectedServiceTable">';
@@ -248,7 +268,10 @@
var selected_id = $(this).attr('id');
selectedPriceElement = document.getElementById('servicePrice_'+selected_id).innerHTML;
totalPrice = totalPrice + parseInt(selectedPriceElement);
selectedNameElement = document.getElementById('serviceName_'+selected_id).innerHTML;
var nameNode = document.getElementById('serviceName_'+selected_id);
// Prefer the full name: the visible label may have had its
// category prefix stripped for readability.
selectedNameElement = nameNode.getAttribute('data-service-name') || nameNode.innerHTML;
selectedServices = selectedServices + '<div class="totalServiceRow"><div class="selectedServiceCol totalSelectedServiceName">'+selectedNameElement+'</div><div class="selectedServiceCol totalSelectedServicePrice">'+selectedPriceElement+' kr</div></div>';
var selectedTotalTime = $('#servicelength').val();
@@ -356,8 +379,13 @@
},
}).done(function(result){
$('#workerTable').html(result.workerListShow);
var firstWorker = result.workers;
setWorker(firstWorker[0].worker_id)
if(result.workers && result.workers.length > 0){
setWorker(result.workers[0].worker_id);
}
else{
$('#workerTable').html('<div class="noWorkerMessage">Nincs elérhető munkatárs ehhez a szolgáltatáshoz.</div>');
setWorker('');
}
});
}
@@ -95,8 +95,8 @@ if (is_array($services) && count($services) > 0) {
foreach ($groups[$label] as $serviceItem) {
?>
<div class="serviceRow">
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>">
<?php echo $serviceItem->service_name; ?>
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>" data-service-name="<?php echo htmlspecialchars($serviceItem->service_name, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo vertical_service_label(isset($vertical) ? $vertical : NULL, $serviceItem); ?>
</div>
<div class="serviceCol">
@@ -235,6 +235,26 @@ if (is_array($services) && count($services) > 0) {
}
});
<?php if ( ! empty($vertical['single_service_booking'])): ?>
/* Single-service verticals (massage): a guest books one treatment at a
time, so selecting one clears and disables the rest. Clicking the
selected one again releases everything - the escape hatch for a
mis-click. Bound BEFORE the totals handler below, so that handler
always sees the corrected state. */
$('.service').click(function(){
if($(this).is(':checked')){
$('.service').not(this)
.prop('checked', false)
.prop('disabled', true)
.closest('.serviceRow').addClass('serviceRowDisabled');
}
else{
$('.service').prop('disabled', false);
$('.serviceRow').removeClass('serviceRowDisabled');
}
});
<?php endif; ?>
$('.service').click(function(){
var totalPrice = 0;
var selectedServices = '<div class="selectedServiceTable">';
@@ -248,7 +268,10 @@ if (is_array($services) && count($services) > 0) {
var selected_id = $(this).attr('id');
selectedPriceElement = document.getElementById('servicePrice_'+selected_id).innerHTML;
totalPrice = totalPrice + parseInt(selectedPriceElement);
selectedNameElement = document.getElementById('serviceName_'+selected_id).innerHTML;
var nameNode = document.getElementById('serviceName_'+selected_id);
// Prefer the full name: the visible label may have had its
// category prefix stripped for readability.
selectedNameElement = nameNode.getAttribute('data-service-name') || nameNode.innerHTML;
selectedServices = selectedServices + '<div class="totalServiceRow"><div class="selectedServiceCol totalSelectedServiceName">'+selectedNameElement+'</div><div class="selectedServiceCol totalSelectedServicePrice">'+selectedPriceElement+' kr</div></div>';
var selectedTotalTime = $('#servicelength').val();
@@ -354,8 +377,13 @@ if (is_array($services) && count($services) > 0) {
},
}).done(function(result){
$('#workerTable').html(result.workerListShow);
var firstWorker = result.workers;
setWorker(firstWorker[0].worker_id)
if(result.workers && result.workers.length > 0){
setWorker(result.workers[0].worker_id);
}
else{
$('#workerTable').html('<div class="noWorkerMessage">Ingen ledige medarbeidere for denne tjenesten.</div>');
setWorker('');
}
});
}
+19 -24
View File
@@ -1,30 +1,25 @@
<section class="tiles">
<article class="style2">
<?php
/**
* Home tiles are driven by the vertical registry: every enabled vertical
* flagged 'public' gets a tile, ordered by 'sort'. Adding a fourth vertical
* is a config entry, not an edit here.
*
* The <img> is only a size spacer - the visible artwork is the CSS
* .tiles article.styleN > .image:before background, keyed off tile_class.
*
* The 'no/' language prefix matches the previous hardcoded links; the home
* page has no language variant of its own.
*/
foreach(vertical_public() as $verticalItem):
?>
<article class="<?php echo $verticalItem['tile_class'];?>">
<span class="image">
<img src="<?php echo SITEURL;?>assets/img/pic03.jpg" alt="" />
<img src="<?php echo SITEURL.$verticalItem['tile_spacer'];?>" alt="" />
</span>
<a href="<?php echo SITEURL;?>no/barber">
<h2>BARBER</h2>
<!--div class="content">
<p>Sed nisl arcu euismod sit amet nisi lorem etiam dolor veroeros et feugiat.</p>
</div-->
</a>
</article>
<article class="style1">
<span class="image">
<img src="<?php echo SITEURL;?>assets/img/pic09.jpg" alt="" />
</span>
<a href="<?php echo SITEURL;?>no/beauty">
<h2>BEAUTY</h2>
<!--div class="content">
<p>Sed nisl arcu euismod sit amet nisi lorem etiam dolor veroeros et feugiat.</p>
</div-->
<a href="<?php echo SITEURL.'no/'.$verticalItem['slug'];?>">
<h2><?php echo htmlspecialchars(vertical_label($verticalItem, 'en'), ENT_QUOTES, 'UTF-8');?></h2>
</a>
</article>
<?php endforeach; ?>
</section>
@@ -0,0 +1,293 @@
<?php
/* -----------------------------------------------------------------------
* Massage vertical landing content - English.
*
* Bio, treatment names and descriptions supplied by the salon.
* Generated from one template so massage-form-no / -en / -hu stay
* structurally identical.
*
* DURATIONS: the price list shows the treatment time from the service NAME
* (50 / 80 / 110 min). services.service_time holds the longer BOOKED slot
* (60 / 90 / 120 min) which covers changing and payment, and is never
* rendered to the guest. See prices_note below.
*
* Still a placeholder: assets/img/massage/masseur.png (therapist portrait),
* assets/img/massage/banner.jpg and map.jpg.
* -------------------------------------------------------------------- */
?>
<!-- Banner -->
<section id="banner">
<img src="<?php echo base_url();?>assets/img/massage/banner.jpg" style="width: 100%;" />
</section>
<section id="mobil-menu">
<div class="mobil-menucont">
<ul>
<li class="mobil-menu-book"><a href="<?php echo SITEURL;?>en/booking/massage">BOOK TIME</a></li>
<li class=""><a href="<?php echo $vertical['facebook'];?>" class="icon brands fa-facebook-f"><span class="label">Facebook</span></a></li>
<li class=""><a href="<?php echo $vertical['instagram'];?>" class="icon brands fa-instagram"><span class="label">Instagram</span></a></li>
</ul>
</div>
</section>
<!-- About Us -->
<section id="aboutus" class="wrapper style1">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
<h2>About us</h2>
</header>
<?php /* Owner block, lifted verbatim from beauty-form-<lang>.php at
generation time so all three verticals introduce Evelin
identically. Do not edit here - edit the beauty file and
regenerate, or the two will drift. */ ?>
<div class="bemutatkozas">
<div class="bem-bal">
<p>
My name is Evelin Bence, leader and creator of Studio Beve. I consider it
important that our guests during their stay – resting a little from the noise of the
world – recharge their batteries and return to everyday life beautified inside as
well as outside.
</p>
<p>
Quality time, maximum attention, the most professional tools, together with
professionalism and expertise, stand for the basis of our philosophy and
services.
</p>
<p>
I believe that beauty, fashion, and trend are not gender-specific, so we have the
opportunity for everyone to become beautiful in one place, regardless of gender.
And among our Barber, Beauty and Massage departments, you can find the right services
for you.
</p>
<p>
With nearly 10 years of experience in the beauty industry, including many years
of international experience, I thought it was time to open my own studio here in
Trondheim at Innheredsveien 92, where guests can park free of charge during
the treatments.
</p>
<p>
It is an extra pleasure for me that I managed to find a traditional place where
there was already a beauty salon in the 50s. Therefore, I now have the
opportunity to carry on this tradition in a modern environment.
Become beautiful with us. We look forward to seeing you.
</p>
</div>
<div class="bem-jobb">
<img src="<?php echo base_url();?>assets/img/evelin_beauty.JPG">
<h4>Evelin Bence <br> <span>Owner, Studio Beve</span></h4>
</div>
</div>
<?php /* Therapist sub-section, following the per-worker pattern
barber/beauty use: circular portrait + name, then the bio
with a photo strip alongside. */ ?>
<div class="dolgozo-nev">
<hr>
<img src="<?php echo base_url();?>assets/img/massage/vincent/vincent.jpg" class="dolgozo-kep">
<h4>VINCENT Pusch <span>- Massage Therapist</span></h4>
</div>
<div class="bemutatkozas-dolgozo">
<div class="bem-dolgozo-bal">
<p>My interest in massage, manual treatment techniques, and the way the human body functions has been with me since 2011 and has, over the years, become a central part of my professional career.</p>
<p>I completed my physiotherapy education in Germany. I began my professional career at a military hospital, where I worked as a physiotherapist and manual therapist. I later continued my work at a multidisciplinary clinic before establishing and running my own private practice in Germany until I moved to Norway.</p>
<p>Over the years, I have continuously expanded my professional knowledge and experience. In addition to physiotherapy and manual therapy, I have worked with sports physiotherapy and, since 2019, also with craniosacral therapy and osteopathy. Continuous professional development and learning new methods are important to me.</p>
<p>I believe that every person and every body is different, which is why I always base my treatments on each client&rsquo;s individual needs and goals. I select and combine different massage and manual techniques accordingly, whether the goal is relaxation, relieving muscle tension caused by everyday or physical strain, recovery after exercise, or simply giving the body time to recharge.</p>
<p>I have been living in Trondheim since May 2026. The process of obtaining Norwegian recognition of my foreign physiotherapy qualification is currently ongoing. At Studio Beve, I currently work as a massage therapist and offer massage and other manual treatments that do not require Norwegian authorization as healthcare personnel. Once Norwegian authorization has been obtained, my professional activities and the range of treatments I can offer may be expanded further.</p>
<p>I look forward to getting to know Studio Beve&rsquo;s clients and using my professional experience to provide attentive, individually tailored treatments.</p>
</div>
<div class="bem-dolgozo-jobb">
<img src="<?php echo base_url();?>assets/img/massage/vincent/1.jpg">
<img src="<?php echo base_url();?>assets/img/massage/vincent/2.jpg">
<img src="<?php echo base_url();?>assets/img/massage/vincent/3.jpg">
<img src="<?php echo base_url();?>assets/img/massage/vincent/4.jpg">
</div>
</div>
</div>
</div>
</section>
<!-- Treatments -->
<section id="treatments" class="wrapper style2">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
<h2 class="nyitva">Treatments</h2>
</header>
</div>
<div class="service-block">
<div class="service-area">
<div class="one-third service-part1">
<div class="service-inner">
<div class="service service1">
<div class="service-text service-text1">
<div class="service-icon icon1"></div>
<div class="service-content">
<div class="main-title">Classic Swedish Massage</div>
<div class="sub-title-serv">full body, relaxation and well-being</div>
</div>
</div>
<div class="service-text service-text2">
<div class="service-icon icon2"></div>
<div class="service-content">
<div class="main-title">Sports Massage</div>
<div class="sub-title-serv">for active individuals and athletes</div>
</div>
</div>
<div class="service-text service-text3">
<div class="service-icon icon3"></div>
<div class="service-content">
<div class="main-title">Deep Tissue Massage</div>
<div class="sub-title-serv">deeper muscles and connective tissue</div>
</div>
</div>
</div>
</div>
</div>
<div class="one-third service-part2">
<div class="service-inner">
<div class="service service2">
<a href="<?php echo SITEURL;?>en/booking/massage" class="banner-anchor"><img src="<?php echo base_url();?>assets/img/service-banner-massage.png" alt="massage" class="banner-image1"></a>
</div>
</div>
</div>
<div class="one-third service-part3">
<div class="service-inner">
<div class="service service3">
<div class="service-text service-text1">
<div class="service-icon icon1"></div>
<div class="service-content">
<div class="main-title">Work Recovery Massage</div>
<div class="sub-title-serv">office and physical work</div>
</div>
</div>
<div class="service-text service-text2">
<div class="service-icon icon2"></div>
<div class="service-content">
<div class="main-title">Studio Beve Signature</div>
<div class="sub-title-serv">individually tailored treatment</div>
</div>
</div>
<div class="service-text service-text3">
<div class="service-icon icon3"></div>
<div class="service-content">
<div class="main-title">Craniosacral Treatment</div>
<div class="sub-title-serv">gentle, holistic manual treatment</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Prices -->
<section id="one" class="wrapper style1">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
<h2>Prices</h2>
<h4 class="prices-note">The stated treatment time is hands-on treatment time. The appointment reserved for you is slightly longer, allowing time to change and to pay.</h4>
</header>
<?php
/**
* Grouped by treatment type. Two deliberate differences from the
* barber/beauty price lists, both because massage sells the SAME
* treatment at two or three lengths:
*
* 1. The description is printed ONCE per group rather than once
* per duration, so a three-length treatment does not repeat
* its blurb three times.
* 2. The row label is the duration alone. services.service_name
* has to stay self-describing ("Klassisk svensk massasje -
* 50 min") because the booking form and the confirmation
* email render it with no category around it - but here the
* category heading is directly above, so repeating it just
* makes every row wrap.
*
* $services arrives ordered by service_id and the catalogue is
* inserted grouped, so a simple change-detect is enough.
*/
$prevousCategory = '';
if(is_array($services)){
foreach($services as $serviceItem){
if($serviceItem->service_category != $prevousCategory){
if($prevousCategory !== ''){
echo '<div style="clear:both;"></div>';
}
?>
<div class="prices-category" style="width: 100%">
<h4><?php echo $serviceItem->service_category;?></h4>
<p class="category-intro"><?php echo $serviceItem->service_description;?></p>
</div>
<?php
}
// Same rule as the booking form: the category heading is
// directly above, so the row need not repeat it.
$rowLabel = vertical_service_label($vertical, $serviceItem);
?>
<div class="one-half">
<div class="category-content">
<div class="category-text-left">
<div class="main-title"><a><?php echo $rowLabel;?></a></div>
</div>
<div class="category-text-right">
<div class="price">kr <?php echo $serviceItem->service_price;?></div>
</div>
</div>
</div>
<?php
$prevousCategory = $serviceItem->service_category;
}
echo '<div style="clear:both;"></div>';
}
?>
</div>
</div>
</section>
<!-- Opening hours -->
<section id="two" class="wrapper style2">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
<h2 class="nyitva">Opening Hours</h2>
<h4>Free customer parking during your treatment</h4>
</header>
</div>
<div class="nyitvatartas">
<div class="about-content">
<div class="about-text-left">
<div class="title">Weekdays</div>
</div>
<div class="about-text-right">
<div class="time">10:00 - 18:00</div>
</div>
</div>
<div class="about-content">
<div class="about-text-left">
<div class="title">Saturday</div>
</div>
<div class="about-text-right">
<div class="time">10:00 - 16:00</div>
</div>
</div>
<div class="about-content">
<div class="about-text-left">
<div class="title">Sunday</div>
</div>
<div class="about-text-right">
<div class="closed">Closed</div>
</div>
</div>
</div>
</div>
</section>
@@ -0,0 +1,291 @@
<?php
/* -----------------------------------------------------------------------
* Massage vertical landing content - Hungarian.
*
* Bio, treatment names and descriptions supplied by the salon.
* Generated from one template so massage-form-no / -en / -hu stay
* structurally identical.
*
* DURATIONS: the price list shows the treatment time from the service NAME
* (50 / 80 / 110 min). services.service_time holds the longer BOOKED slot
* (60 / 90 / 120 min) which covers changing and payment, and is never
* rendered to the guest. See prices_note below.
*
* Still a placeholder: assets/img/massage/masseur.png (therapist portrait),
* assets/img/massage/banner.jpg and map.jpg.
* -------------------------------------------------------------------- */
?>
<!-- Banner -->
<section id="banner">
<img src="<?php echo base_url();?>assets/img/massage/banner.jpg" style="width: 100%;" />
</section>
<section id="mobil-menu">
<div class="mobil-menucont">
<ul>
<li class="mobil-menu-book"><a href="<?php echo SITEURL;?>hu/booking/massage">IDŐPONTFOGLALÁS</a></li>
<li class=""><a href="<?php echo $vertical['facebook'];?>" class="icon brands fa-facebook-f"><span class="label">Facebook</span></a></li>
<li class=""><a href="<?php echo $vertical['instagram'];?>" class="icon brands fa-instagram"><span class="label">Instagram</span></a></li>
</ul>
</div>
</section>
<!-- About Us -->
<section id="aboutus" class="wrapper style1">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
<h2>Rólunk</h2>
</header>
<?php /* Owner block, lifted verbatim from beauty-form-<lang>.php at
generation time so all three verticals introduce Evelin
identically. Do not edit here - edit the beauty file and
regenerate, or the two will drift. */ ?>
<div class="bemutatkozas">
<div class="bem-bal">
<p>
Bence Evelin vagyok a Studio Beve vezetője és megálmodója. Fontosnak
tartom, hogy a vendégek a nálunk töltött idő alatt – egy kicsit megpihenve a világ zajától – feltöltődjenek, és kívül-belül egszépülve térjenek vissza a mindennapokhoz.
</p>
<p>
A minőségi idő, a maximális figyelem, a legprofesszionálisabb eszközök, a
hivatástudattal és szakértelemmel karöltve jelentik a filozófiánk és
szolgáltatásaink alapjait.
</p>
<p>
Hiszem, hogy a szépség, a divat és a trend nem nemhez kötött, így nálunk
lehetőség van arra, hogy mindenki egy helyen szépülhessen nemtől függetlenül.
A Barber, Beauty és Masszázs részlegeink között pedig megtalálhatjátok a számotokra
megfelelő szolgáltatásokat.
</p>
<p>
Közel 10 éves szépségipari, és közte több éves nemzetközi tapasztalattal
elérkezettnek láttam az időt, hogy megnyissam saját stúdiómat itt
Trondheimben, az Innheredsveien 92 szám alatt, ahol a vendégeknek lehetősége
van ingyenes parkolásra is a kezelések időtartama alatt.
</p>
<p>
Extra öröm számomra, hogy egy olyan tradicionális helyet sikerült találnom,
ahol az 50-es években már szépségszalon működött, ennek köszönhetően a
hagyományt, modern környezetben lehetőségem van tovább vinni.
Szépülj velünk, szeretettel várunk.
</p>
</div>
<div class="bem-jobb">
<img src="<?php echo base_url();?>assets/img/evelin_beauty.JPG">
<h4>Bence Evelin <br> <span>Tulajdonos, Studio Beve</span></h4>
</div>
</div>
<?php /* Therapist sub-section, following the per-worker pattern
barber/beauty use: circular portrait + name, then the bio
with a photo strip alongside. */ ?>
<div class="dolgozo-nev">
<hr>
<img src="<?php echo base_url();?>assets/img/massage/vincent/vincent.jpg" class="dolgozo-kep">
<h4>VINCENT Pusch <span>- Masszőr</span></h4>
</div>
<div class="bemutatkozas-dolgozo">
<div class="bem-dolgozo-bal">
<p>A masszázs, a manuális kezelések és az emberi test működése iránti érdeklődésem 2011 óta kísér, és az évek során szakmai pályám meghatározó részévé vált.</p>
<p>Fizioterápiás végzettségemet Németországban szereztem. Pályafutásomat katonai kórházban kezdtem, ahol fizioterapeutaként és manuálterapeutaként dolgoztam. Később multidiszciplináris rendelőben folytattam a munkámat, majd saját magánpraxist vezettem Németországban, mielőtt Norvégiába költöztem.</p>
<p>Az évek során folyamatosan bővítettem szakmai ismereteimet és tapasztalataimat. A fizioterápia és manuálterápia mellett sportfizioterápiával, valamint 2019 óta craniosacralis terápiával és oszteopátiával is foglalkozom. Fontos számomra a folyamatos szakmai fejlődés és az új módszerek megismerése.</p>
<p>Hiszek abban, hogy minden ember és minden test más, ezért a kezeléseim során mindig az egyéni igényeket és célokat tartom szem előtt. A különböző masszázs- és manuális technikákat ennek megfelelően választom ki és kombinálom, legyen szó relaxációról, a mindennapi vagy fizikai terhelés következtében kialakult izomfeszültségről, sport utáni regenerációról vagy egyszerűen a testi feltöltődésről.</p>
<p>2026 májusa óta élek Trondheimben. Külföldön megszerzett fizioterápiás képesítésem norvég elismertetése jelenleg folyamatban van. A Studio Bevében jelenleg masszőrként végzem szakmai tevékenységemet, ahol masszázs- és olyan manuális kezeléseket kínálok, amelyekhez nem szükséges norvég egészségügyi engedély. A norvég engedély megszerzésével szakmai tevékenységem és az általam kínált kezelések köre a jövőben tovább bővülhet.</p>
<p>Örömmel várom, hogy a Studio Beve vendégeit is megismerhessem, és szakmai tapasztalatommal személyre szabott, figyelmes kezelést nyújthassak számukra.</p>
</div>
<div class="bem-dolgozo-jobb">
<img src="<?php echo base_url();?>assets/img/massage/vincent/1.jpg">
<img src="<?php echo base_url();?>assets/img/massage/vincent/2.jpg">
<img src="<?php echo base_url();?>assets/img/massage/vincent/3.jpg">
<img src="<?php echo base_url();?>assets/img/massage/vincent/4.jpg">
</div>
</div>
</div>
</div>
</section>
<!-- Treatments -->
<section id="treatments" class="wrapper style2">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
<h2 class="nyitva">Szolgáltatások</h2>
</header>
</div>
<div class="service-block">
<div class="service-area">
<div class="one-third service-part1">
<div class="service-inner">
<div class="service service1">
<div class="service-text service-text1">
<div class="service-icon icon1"></div>
<div class="service-content">
<div class="main-title">Klasszikus svédmasszázs</div>
<div class="sub-title-serv">teljes test, lazítás és feltöltődés</div>
</div>
</div>
<div class="service-text service-text2">
<div class="service-icon icon2"></div>
<div class="service-content">
<div class="main-title">Sportmasszázs</div>
<div class="sub-title-serv">aktív életmódhoz és sportolóknak</div>
</div>
</div>
<div class="service-text service-text3">
<div class="service-icon icon3"></div>
<div class="service-content">
<div class="main-title">Mélyszöveti masszázs</div>
<div class="sub-title-serv">mélyebb izmok és kötőszövet</div>
</div>
</div>
</div>
</div>
</div>
<div class="one-third service-part2">
<div class="service-inner">
<div class="service service2">
<a href="<?php echo SITEURL;?>hu/booking/massage" class="banner-anchor"><img src="<?php echo base_url();?>assets/img/service-banner-massage.png" alt="massage" class="banner-image1"></a>
</div>
</div>
</div>
<div class="one-third service-part3">
<div class="service-inner">
<div class="service service3">
<div class="service-text service-text1">
<div class="service-icon icon1"></div>
<div class="service-content">
<div class="main-title">Irodai és fizikai munka utáni masszázs</div>
<div class="sub-title-serv">ülőmunkához és fizikai terheléshez</div>
</div>
</div>
<div class="service-text service-text2">
<div class="service-icon icon2"></div>
<div class="service-content">
<div class="main-title">Studio Beve Signature</div>
<div class="sub-title-serv">személyre szabott kezelés</div>
</div>
</div>
<div class="service-text service-text3">
<div class="service-icon icon3"></div>
<div class="service-content">
<div class="main-title">Craniosacralis kezelés</div>
<div class="sub-title-serv">gyengéd, holisztikus manuális kezelés</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Prices -->
<section id="one" class="wrapper style1">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
<h2>Árlista</h2>
<h4 class="prices-note">A feltüntetett kezelési idő a tényleges masszázsidőt jelenti. A lefoglalt időpont ennél valamivel hosszabb, hogy legyen idő az át&shy;öltözésre és a fizetésre.</h4>
</header>
<?php
/**
* Grouped by treatment type. Two deliberate differences from the
* barber/beauty price lists, both because massage sells the SAME
* treatment at two or three lengths:
*
* 1. The description is printed ONCE per group rather than once
* per duration, so a three-length treatment does not repeat
* its blurb three times.
* 2. The row label is the duration alone. services.service_name
* has to stay self-describing ("Klassisk svensk massasje -
* 50 min") because the booking form and the confirmation
* email render it with no category around it - but here the
* category heading is directly above, so repeating it just
* makes every row wrap.
*
* $services arrives ordered by service_id and the catalogue is
* inserted grouped, so a simple change-detect is enough.
*/
$prevousCategory = '';
if(is_array($services)){
foreach($services as $serviceItem){
if($serviceItem->service_category != $prevousCategory){
if($prevousCategory !== ''){
echo '<div style="clear:both;"></div>';
}
?>
<div class="prices-category" style="width: 100%">
<h4><?php echo $serviceItem->service_category;?></h4>
<p class="category-intro"><?php echo $serviceItem->service_description;?></p>
</div>
<?php
}
// Same rule as the booking form: the category heading is
// directly above, so the row need not repeat it.
$rowLabel = vertical_service_label($vertical, $serviceItem);
?>
<div class="one-half">
<div class="category-content">
<div class="category-text-left">
<div class="main-title"><a><?php echo $rowLabel;?></a></div>
</div>
<div class="category-text-right">
<div class="price">kr <?php echo $serviceItem->service_price;?></div>
</div>
</div>
</div>
<?php
$prevousCategory = $serviceItem->service_category;
}
echo '<div style="clear:both;"></div>';
}
?>
</div>
</div>
</section>
<!-- Opening hours -->
<section id="two" class="wrapper style2">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
<h2 class="nyitva">Nyitvatartás</h2>
<h4>Ingyenes vendégparkolás a kezelés ideje alatt</h4>
</header>
</div>
<div class="nyitvatartas">
<div class="about-content">
<div class="about-text-left">
<div class="title">Hétköznap</div>
</div>
<div class="about-text-right">
<div class="time">10:00 - 18:00</div>
</div>
</div>
<div class="about-content">
<div class="about-text-left">
<div class="title">Szombat</div>
</div>
<div class="about-text-right">
<div class="time">10:00 - 16:00</div>
</div>
</div>
<div class="about-content">
<div class="about-text-left">
<div class="title">Vasárnap</div>
</div>
<div class="about-text-right">
<div class="closed">Zárva</div>
</div>
</div>
</div>
</div>
</section>
@@ -0,0 +1,293 @@
<?php
/* -----------------------------------------------------------------------
* Massage vertical landing content - Norwegian (bokmål).
*
* Bio, treatment names and descriptions supplied by the salon.
* Generated from one template so massage-form-no / -en / -hu stay
* structurally identical.
*
* DURATIONS: the price list shows the treatment time from the service NAME
* (50 / 80 / 110 min). services.service_time holds the longer BOOKED slot
* (60 / 90 / 120 min) which covers changing and payment, and is never
* rendered to the guest. See prices_note below.
*
* Still a placeholder: assets/img/massage/masseur.png (therapist portrait),
* assets/img/massage/banner.jpg and map.jpg.
* -------------------------------------------------------------------- */
?>
<!-- Banner -->
<section id="banner">
<img src="<?php echo base_url();?>assets/img/massage/banner.jpg" style="width: 100%;" />
</section>
<section id="mobil-menu">
<div class="mobil-menucont">
<ul>
<li class="mobil-menu-book"><a href="<?php echo SITEURL;?>no/booking/massage">BESTILL TIME</a></li>
<li class=""><a href="<?php echo $vertical['facebook'];?>" class="icon brands fa-facebook-f"><span class="label">Facebook</span></a></li>
<li class=""><a href="<?php echo $vertical['instagram'];?>" class="icon brands fa-instagram"><span class="label">Instagram</span></a></li>
</ul>
</div>
</section>
<!-- About Us -->
<section id="aboutus" class="wrapper style1">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
<h2>Om oss</h2>
</header>
<?php /* Owner block, lifted verbatim from beauty-form-<lang>.php at
generation time so all three verticals introduce Evelin
identically. Do not edit here - edit the beauty file and
regenerate, or the two will drift. */ ?>
<div class="bemutatkozas">
<div class="bem-bal">
<p>
Mitt navn er Evelin Bence, lederen og skaperen av Studio Beve. Jeg anser det som
viktig at våre gjester under oppholdet hos oss – mens de stenger ute litt av støyen
fra omverdenen – kan lade sine batterier og kan vende tilbake til hverdagen
forskjønnet både innvendig og utvendig.
</p>
<p>
Kvalitetstid, maksimal oppmerksomhet, de mest profesjonelle verktøyene, hånd
i hånd med profesjonalitet og kompetanse, utgjør grunnlaget for vår filosofi og våre
tjenester.
</p>
<p>
Jeg mener at skjønnhet, mote og trend ikke er kjønnsspesifikke, og hos oss har
derfor alle mulighet til å bli vakrere på ett sted, uavhengig av kjønn. Hos Studio
Beve Barber, Studio Beve Beauty og Studio Beve Massasje kan dere finne de tjenestene som er riktige
for dere.
</p>
<p>
Med nesten 10 års erfaring fra skjønnhetsindustrien, inkludert mange års
internasjonal erfaring, tenkte jeg det var på tide å åpne mitt eget studio her i
Trondheim i Innheredsveien 92, hvor gjestene har mulighet til å parkere gratis
under behandlingene.
</p>
<p>
Det er en ekstra glede for meg at jeg klarte å finne et tradisjonelt sted hvor det
allerede var en skjønnhetssalong på 50-tallet. Derfor har jeg nå muligheten til å
videreføre denne tradisjonen i et moderne miljø.
Bli vakker hos oss. Vi ser frem til å ønske deg velkommen.
</p>
</div>
<div class="bem-jobb">
<img src="<?php echo base_url();?>assets/img/evelin_beauty.JPG">
<h4>Evelin Bence <br> <span>Eieren, Studio Beve</span></h4>
</div>
</div>
<?php /* Therapist sub-section, following the per-worker pattern
barber/beauty use: circular portrait + name, then the bio
with a photo strip alongside. */ ?>
<div class="dolgozo-nev">
<hr>
<img src="<?php echo base_url();?>assets/img/massage/vincent/vincent.jpg" class="dolgozo-kep">
<h4>VINCENT Pusch <span>- Massør</span></h4>
</div>
<div class="bemutatkozas-dolgozo">
<div class="bem-dolgozo-bal">
<p>Min interesse for massasje, manuelle behandlingsteknikker og hvordan menneskekroppen fungerer, har fulgt meg siden 2011 og har gjennom årene blitt en sentral del av min faglige karriere.</p>
<p>Jeg tok min fysioterapiutdanning i Tyskland. Jeg startet yrkeskarrieren min ved et militærsykehus, hvor jeg arbeidet som fysioterapeut og manuellterapeut. Senere fortsatte jeg arbeidet ved en tverrfaglig klinikk, før jeg etablerte og drev min egen private praksis i Tyskland frem til jeg flyttet til Norge.</p>
<p>Gjennom årene har jeg kontinuerlig utvidet min faglige kunnskap og erfaring. I tillegg til fysioterapi og manuellterapi har jeg arbeidet med idrettsfysioterapi, og siden 2019 også med kraniosakralterapi og osteopati. Kontinuerlig faglig utvikling og det å lære nye metoder er viktig for meg.</p>
<p>Jeg tror på at hvert menneske og hver kropp er forskjellig, og derfor tar jeg alltid utgangspunkt i den enkeltes behov og mål i behandlingene mine. Jeg velger og kombinerer ulike massasje- og manuelle teknikker ut fra dette, enten målet er avslapning, å redusere muskelspenninger som følge av daglig eller fysisk belastning, restitusjon etter trening eller rett og slett å gi kroppen tid til å hente seg inn.</p>
<p>Jeg har bodd i Trondheim siden mai 2026. Prosessen med norsk godkjenning av min utenlandske fysioterapiutdanning pågår for tiden. Hos Studio Beve arbeider jeg i dag som massør og tilbyr massasje og andre manuelle behandlinger som ikke krever norsk autorisasjon som helsepersonell. Når norsk autorisasjon foreligger, vil min faglige virksomhet og behandlingstilbudet jeg kan tilby, kunne utvides ytterligere.</p>
<p>Jeg gleder meg til å bli kjent med Studio Beves kunder og til å bruke min faglige erfaring til å tilby individuelt tilpassede og omsorgsfulle behandlinger.</p>
</div>
<div class="bem-dolgozo-jobb">
<img src="<?php echo base_url();?>assets/img/massage/vincent/1.jpg">
<img src="<?php echo base_url();?>assets/img/massage/vincent/2.jpg">
<img src="<?php echo base_url();?>assets/img/massage/vincent/3.jpg">
<img src="<?php echo base_url();?>assets/img/massage/vincent/4.jpg">
</div>
</div>
</div>
</div>
</section>
<!-- Treatments -->
<section id="treatments" class="wrapper style2">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo base_url();?>assets/img/massage/ollo_rose.png">
<h2 class="nyitva">Behandlinger</h2>
</header>
</div>
<div class="service-block">
<div class="service-area">
<div class="one-third service-part1">
<div class="service-inner">
<div class="service service1">
<div class="service-text service-text1">
<div class="service-icon icon1"></div>
<div class="service-content">
<div class="main-title">Klassisk svensk massasje</div>
<div class="sub-title-serv">helkropp, avslapning og velvære</div>
</div>
</div>
<div class="service-text service-text2">
<div class="service-icon icon2"></div>
<div class="service-content">
<div class="main-title">Sportsmassasje</div>
<div class="sub-title-serv">for aktive personer og idrettsutøvere</div>
</div>
</div>
<div class="service-text service-text3">
<div class="service-icon icon3"></div>
<div class="service-content">
<div class="main-title">Deep Tissue-massasje</div>
<div class="sub-title-serv">dypere muskulatur og bindevev</div>
</div>
</div>
</div>
</div>
</div>
<div class="one-third service-part2">
<div class="service-inner">
<div class="service service2">
<a href="<?php echo SITEURL;?>no/booking/massage" class="banner-anchor"><img src="<?php echo base_url();?>assets/img/service-banner-massage.png" alt="massage" class="banner-image1"></a>
</div>
</div>
</div>
<div class="one-third service-part3">
<div class="service-inner">
<div class="service service3">
<div class="service-text service-text1">
<div class="service-icon icon1"></div>
<div class="service-content">
<div class="main-title">Arbeidsmassasje</div>
<div class="sub-title-serv">kontor og fysisk arbeid</div>
</div>
</div>
<div class="service-text service-text2">
<div class="service-icon icon2"></div>
<div class="service-content">
<div class="main-title">Studio Beve Signature</div>
<div class="sub-title-serv">individuelt tilpasset behandling</div>
</div>
</div>
<div class="service-text service-text3">
<div class="service-icon icon3"></div>
<div class="service-content">
<div class="main-title">Kraniosakral behandling</div>
<div class="sub-title-serv">skånsom, helhetlig manuell behandling</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</div>
</section>
<!-- Prices -->
<section id="one" class="wrapper style1">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
<h2>Priser</h2>
<h4 class="prices-note">Oppgitt behandlingstid er ren behandlingstid. Timen som reserveres er noe lengre, slik at det er tid til av- og påkledning og betaling.</h4>
</header>
<?php
/**
* Grouped by treatment type. Two deliberate differences from the
* barber/beauty price lists, both because massage sells the SAME
* treatment at two or three lengths:
*
* 1. The description is printed ONCE per group rather than once
* per duration, so a three-length treatment does not repeat
* its blurb three times.
* 2. The row label is the duration alone. services.service_name
* has to stay self-describing ("Klassisk svensk massasje -
* 50 min") because the booking form and the confirmation
* email render it with no category around it - but here the
* category heading is directly above, so repeating it just
* makes every row wrap.
*
* $services arrives ordered by service_id and the catalogue is
* inserted grouped, so a simple change-detect is enough.
*/
$prevousCategory = '';
if(is_array($services)){
foreach($services as $serviceItem){
if($serviceItem->service_category != $prevousCategory){
if($prevousCategory !== ''){
echo '<div style="clear:both;"></div>';
}
?>
<div class="prices-category" style="width: 100%">
<h4><?php echo $serviceItem->service_category;?></h4>
<p class="category-intro"><?php echo $serviceItem->service_description;?></p>
</div>
<?php
}
// Same rule as the booking form: the category heading is
// directly above, so the row need not repeat it.
$rowLabel = vertical_service_label($vertical, $serviceItem);
?>
<div class="one-half">
<div class="category-content">
<div class="category-text-left">
<div class="main-title"><a><?php echo $rowLabel;?></a></div>
</div>
<div class="category-text-right">
<div class="price">kr <?php echo $serviceItem->service_price;?></div>
</div>
</div>
</div>
<?php
$prevousCategory = $serviceItem->service_category;
}
echo '<div style="clear:both;"></div>';
}
?>
</div>
</div>
</section>
<!-- Opening hours -->
<section id="two" class="wrapper style2">
<div class="container">
<div class="inner">
<header class="major">
<img src="<?php echo SITEURL;?>assets/img/massage/ollo_rose.png">
<h2 class="nyitva">Åpningstider</h2>
<h4>Gratis kunderparking under behandlingen</h4>
</header>
</div>
<div class="nyitvatartas">
<div class="about-content">
<div class="about-text-left">
<div class="title">Ukedager</div>
</div>
<div class="about-text-right">
<div class="time">10:00 - 18:00</div>
</div>
</div>
<div class="about-content">
<div class="about-text-left">
<div class="title">Lørdag</div>
</div>
<div class="about-text-right">
<div class="time">10:00 - 16:00</div>
</div>
</div>
<div class="about-content">
<div class="about-text-left">
<div class="title">Søndag</div>
</div>
<div class="about-text-right">
<div class="closed">Stengt</div>
</div>
</div>
</div>
</div>
</section>
@@ -7,11 +7,11 @@
if($firstService) $subpage = $firstService->service_type;
}
if($subpage == 'beauty'){
include(getcwd().'/application/views/includes/beauty-skeleton-top.php');
} else {
include(getcwd().'/application/views/includes/barber-skeleton-top.php');
$vertical = vertical_get($subpage);
if(!$vertical){
$vertical = vertical_get('barber');
}
include(getcwd().'/application/views/includes/'.$vertical['skeleton'].'-skeleton-top.php');
$messages = array(
'no' => 'Din bestilling den <strong>'.$booking->booking_date.'</strong> kl. <strong>'.date('H:i', strtotime($booking->booking_start_time)).'</strong> med <strong>'.htmlspecialchars($worker->worker_name, ENT_QUOTES, 'UTF-8').'</strong> er avbestilt.',
@@ -36,9 +36,5 @@
</div>
<?php
if($subpage == 'beauty'){
include(getcwd().'/application/views/includes/beauty-skeleton-bottom.php');
} else {
include(getcwd().'/application/views/includes/barber-skeleton-bottom.php');
}
include(getcwd().'/application/views/includes/'.$vertical['skeleton'].'-skeleton-bottom.php');
?>
+25 -12
View File
@@ -1,9 +1,11 @@
<?php
if($subpage == 'beauty'){
include(getcwd().'/application/views/includes/beauty-skeleton-top.php');
} else {
include(getcwd().'/application/views/includes/barber-skeleton-top.php');
// Skeleton comes from the vertical registry so a new vertical needs no
// edit here. barber/beauty still resolve to their own skeleton files.
$vertical = isset($vertical) ? $vertical : vertical_get($subpage);
if(!$vertical){
$vertical = vertical_get('barber');
}
include(getcwd().'/application/views/includes/'.$vertical['skeleton'].'-skeleton-top.php');
$lang = $selectedLang;
@@ -341,8 +343,8 @@
$isOtherCategory = ($categorySlug !== '' && $serviceItem->serv_cat_slug !== $categorySlug);
?>
<div class="serviceRow">
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>">
<?php echo htmlspecialchars($serviceItem->service_name); ?>
<div class="serviceCol serviceNameCol" id="serviceName_<?php echo $serviceItem->service_id; ?>" data-service-name="<?php echo htmlspecialchars($serviceItem->service_name, ENT_QUOTES, 'UTF-8'); ?>">
<?php echo htmlspecialchars(vertical_service_label(isset($vertical) ? $vertical : NULL, $serviceItem)); ?>
</div>
<div class="serviceCol">
<input type="time" class="serviceTime" id="time_<?php echo $serviceItem->service_id; ?>" value="<?php echo $serviceItem->service_time; ?>"/>
@@ -462,6 +464,19 @@
});
$('.service').click(function(){
<?php if ( ! empty($vertical['single_service_booking'])): ?>
/* See booking-form-*.php: one treatment at a time for this vertical. */
if($(this).is(':checked')){
$('.service').not(this)
.prop('checked', false)
.prop('disabled', true)
.closest('.serviceRow').addClass('serviceRowDisabled');
}
else{
$('.service').prop('disabled', false);
$('.serviceRow').removeClass('serviceRowDisabled');
}
<?php endif; ?>
recalcServices();
if($('.service').is(':checked')){
var selectedClasses = $(this).attr('class');
@@ -500,7 +515,9 @@
var selected_id = $(this).attr('id');
var priceEl = document.getElementById('servicePrice_'+selected_id).innerHTML.trim();
totalPrice += parseInt(priceEl);
var nameEl = document.getElementById('serviceName_'+selected_id).innerHTML.trim();
var nameNode = document.getElementById('serviceName_'+selected_id);
// Prefer the full name; the visible label may be abbreviated.
var nameEl = (nameNode.getAttribute('data-service-name') || nameNode.innerHTML).trim();
selectedServices += '<div class="totalServiceRow"><div class="selectedServiceCol totalSelectedServiceName">'+nameEl+'</div><div class="selectedServiceCol totalSelectedServicePrice">'+priceEl+' kr</div></div>';
var currentTotal = $('#servicelength').val();
@@ -632,9 +649,5 @@
</script>
<?php
if($subpage == 'beauty'){
include(getcwd().'/application/views/includes/beauty-skeleton-bottom.php');
} else {
include(getcwd().'/application/views/includes/barber-skeleton-bottom.php');
}
include(getcwd().'/application/views/includes/'.$vertical['skeleton'].'-skeleton-bottom.php');
?>
@@ -0,0 +1,11 @@
<?php
/**
* Generic booking error page.
*/
include(getcwd().'/application/views/includes/vertical-skeleton-top.php');
$lang = vertical_lang($selectedLang);
include(getcwd().'/application/views/pages/includes/booking-error-form-'.$lang.'.php');
include(getcwd().'/application/views/includes/vertical-skeleton-bottom.php');
?>
@@ -0,0 +1,11 @@
<?php
/**
* Generic "booking finished" page.
*/
include(getcwd().'/application/views/includes/vertical-skeleton-top.php');
$lang = vertical_lang($selectedLang);
include(getcwd().'/application/views/pages/includes/booking-finished-form-'.$lang.'.php');
include(getcwd().'/application/views/includes/vertical-skeleton-bottom.php');
?>
@@ -0,0 +1,12 @@
<?php
/**
* Generic booking page. The booking form partial is already shared between
* barber and beauty, so only the skeleton is parameterised here.
*/
include(getcwd().'/application/views/includes/vertical-skeleton-top.php');
$lang = vertical_lang($selectedLang);
include(getcwd().'/application/views/pages/includes/booking-form-'.$lang.'.php');
include(getcwd().'/application/views/includes/vertical-skeleton-bottom.php');
?>
@@ -0,0 +1,22 @@
<?php
/**
* Generic vertical landing page.
*
* Expects $vertical and $selectedLang. Mirrors pages/beauty.php, but the
* content partial is chosen from the registry slug rather than hardcoded,
* so a new vertical needs three content files and no new dispatcher.
*/
include(getcwd().'/application/views/includes/vertical-skeleton-top.php');
$lang = vertical_lang($selectedLang);
$contentFile = getcwd().'/application/views/pages/includes/'.$vertical['slug'].'-form-'.$lang.'.php';
if(file_exists($contentFile)){
include($contentFile);
}
else{
include(getcwd().'/application/views/pages/includes/'.$vertical['slug'].'-form-en.php');
}
include(getcwd().'/application/views/includes/vertical-skeleton-bottom.php');
?>