diff --git a/application/config/autoload.php b/application/config/autoload.php index dd4098c..0da0117 100755 --- a/application/config/autoload.php +++ b/application/config/autoload.php @@ -89,7 +89,7 @@ $autoload['drivers'] = array(); | | $autoload['helper'] = array('url', 'file'); */ -$autoload['helper'] = array(); +$autoload['helper'] = array('vertical'); /* | ------------------------------------------------------------------- diff --git a/application/config/routes.php b/application/config/routes.php index 118875b..67abb23 100755 --- a/application/config/routes.php +++ b/application/config/routes.php @@ -60,6 +60,11 @@ $route['profile'] = 'pages/profile'; $route['log-monitor'] = 'Pages/log_monitor'; $route['(:any)/barber'] = 'pages/barber/$1'; $route['(:any)/beauty'] = 'pages/beauty/$1'; +// Verticals added after the registry was introduced dispatch through +// Pages::vertical(), which resolves branding and views from +// application/config/verticals.php. barber and beauty keep their original +// dedicated routes so their output stays byte-identical. +$route['(:any)/massage'] = 'pages/vertical/$1/massage'; $route['(:any)/booking/(:any)'] = 'pages/booking/$1/$2'; $route['(:any)/booking-finished/(:any)'] = 'pages/booking_finished/$1/$2'; $route['email-verification'] = 'pages/email_verification'; diff --git a/application/config/verticals.php b/application/config/verticals.php new file mode 100644 index 0000000..1a3edc7 --- /dev/null +++ b/application/config/verticals.php @@ -0,0 +1,168 @@ +. +| heading the "- BEAUTY" text beside the logo in the header. +| category_slug service_categories.serv_cat_slug that this vertical's +| workers belong to. Informational: the runtime derives the +| worker set from the DB, never from this value. +| +| ONE SLUG ACROSS ALL LANGUAGES +| ----------------------------- +| The language switcher rebuilds URLs by exploding $_SERVER['REQUEST_URI'] and +| reusing segment 2 verbatim (see *-header.php). A per-language slug would +| therefore produce a 404 the moment a visitor switched language. Do not add +| one. If a localised URL is ever needed, add an alias route pointing at the +| same canonical slug. +| +| Note also config.php permitted_uri_chars excludes æ/ø/å, so a Norwegian +| slug containing them would be rejected by CodeIgniter before routing. +*/ + +$config['verticals'] = array( + + 'barber' => array( + 'slug' => 'barber', + 'enabled' => TRUE, + 'public' => TRUE, + 'sort' => 10, + 'category_slug' => 'cosmeric', + + 'label' => array('no' => 'BARBER', 'en' => 'BARBER', 'hu' => 'BARBER'), + 'heading' => '- BARBER', + + 'title' => 'Studio Beve - Beauty & Barber Shop', + 'og_site_name' => 'Studio Beve Barber', + 'og_description' => 'Vippestylist, make up artist, PMU-artist', + 'meta_description' => 'Skjeggtrimming, Barneklipp, Skjeggfarging, Herreklipp, Skinfade Herreklipp, Voks, Beard Trimming, Child Haircut, Beard Dying, Traditional Shave, Men\'s Haircut, Skinfade, Wax, herrefrisør, Trondheim, hårklipp, haircut, barber, barberer, Men\'s hairdresser', + + 'css' => 'barber.css', + 'logo' => 'logo_gold.png', + 'img_dir' => 'barber', + 'map_img' => 'assets/img/barber/map.jpg', + + 'facebook' => 'https://www.facebook.com/studiobevebarber', + 'instagram' => 'https://www.instagram.com/studio.beve.barber/', + + 'tile_class' => 'style2', + 'tile_img' => 'assets/img/barber.jpg', + // The in the home tile is only a size spacer; the visible art is + // the CSS .image:before background. Kept per-vertical so the existing + // tiles render exactly as before. + 'tile_spacer' => 'assets/img/pic03.jpg', + + // Strangler: existing dedicated files, untouched. + 'skeleton' => 'barber', + 'view_landing' => 'pages/barber', + 'view_booking' => 'pages/barber-booking', + 'view_finished' => 'pages/barber-booking-finished', + 'view_error' => 'pages/barber-booking-error', + ), + + 'beauty' => array( + 'slug' => 'beauty', + 'enabled' => TRUE, + 'public' => TRUE, + 'sort' => 20, + 'category_slug' => 'cosmeric', + + 'label' => array('no' => 'BEAUTY', 'en' => 'BEAUTY', 'hu' => 'BEAUTY'), + 'heading' => '- BEAUTY', + + 'title' => 'Studio Beve - Beauty', + 'og_site_name' => 'Studio Beve', + 'og_description' => 'Vippestylist, make up artist, PMU-artist', + 'meta_description' => 'Voksing, Sminke, Vippeextensions, Pudderbryn, PMU-Tatovering, Øyenbrynstyling, Vippestyling, Powder Brows, permanent, PMU Makeup, Makeup, Wax, Eyelash Extension, Eyebrow styling, Eyelash Styling, Wedding makeup, Brow lamination, Lash dying, Brow forming, Henna dying, Lash lift, Naturlig sminke, Bryllupssminke, Laminering, Henna farging, Vippeløft, Forming av bryn, Farging av vipper, Trondheim', + + 'css' => 'beauty.css', + 'logo' => 'logo.png', + 'img_dir' => 'beauty', + 'map_img' => 'assets/img/beauty/map.jpg', + + 'facebook' => 'https://www.facebook.com/studiobevebeauty', + 'instagram' => 'https://www.instagram.com/studio.beve.beauty/', + + 'tile_class' => 'style1', + 'tile_img' => 'assets/img/beauty.jpg', + 'tile_spacer' => 'assets/img/pic09.jpg', + + // Strangler: existing dedicated files, untouched. + 'skeleton' => 'beauty', + 'view_landing' => 'pages/beauty', + 'view_booking' => 'pages/beauty-booking', + 'view_finished' => 'pages/beauty-booking-finished', + 'view_error' => 'pages/beauty-booking-error', + ), + + 'massage' => array( + 'slug' => 'massage', + 'enabled' => TRUE, + // Launch switch: the tile CSS now supports three tiles, so massage is live. + 'public' => TRUE, + 'sort' => 30, + 'category_slug' => 'massage', + // Row labels in grouped service lists drop the category prefix, so a + // heading of "Klassisk svensk massasje" is followed by "50 min" rather + // than the whole name again. barber/beauty omit this and are unchanged. + 'strip_category_prefix' => TRUE, + // A guest books one massage at a time: selecting a treatment clears and + // disables the others. Enforced in the UI *and* in booking_process(). + // barber/beauty omit this and keep multi-select. + 'single_service_booking' => TRUE, + + 'label' => array('no' => 'MASSASJE', 'en' => 'MASSAGE', 'hu' => 'MASSZÁZS'), + 'heading' => '- MASSAGE', + + 'title' => 'Studio Beve - Massage', + 'og_site_name' => 'Studio Beve Massage', + 'og_description' => 'Massasje, klassisk massasje, dyp vevsmassasje', + 'meta_description' => 'Massasje, Klassisk massasje, Dypvevsmassasje, Ryggmassasje, Nakkemassasje, Idrettsmassasje, Avslappende massasje, Massage, Deep tissue massage, Sports massage, Relaxing massage, Back massage, Masszázs, Svédmasszázs, Frissítő masszázs, Trondheim', + + 'css' => 'massage.css', + 'logo' => 'logo.png', + 'img_dir' => 'massage', + 'map_img' => 'assets/img/massage/map.jpg', + + 'facebook' => 'https://www.facebook.com/studiobevebeauty', + 'instagram' => 'https://www.instagram.com/studio.beve.beauty/', + + 'tile_class' => 'style3', + 'tile_img' => 'assets/img/massage.jpg', + 'tile_spacer' => 'assets/img/pic03.jpg', + + // The generic parameterised views. + 'skeleton' => 'vertical', + 'view_landing' => 'pages/vertical-landing', + 'view_booking' => 'pages/vertical-booking', + 'view_finished' => 'pages/vertical-booking-finished', + 'view_error' => 'pages/vertical-booking-error', + ), + +); diff --git a/application/controllers/Admin.php b/application/controllers/Admin.php index 5f6dee0..8db679c 100755 --- a/application/controllers/Admin.php +++ b/application/controllers/Admin.php @@ -298,8 +298,9 @@ class Admin extends CI_Controller { } if(isset($_POST['addNewService'])){ + $allowedServiceTypes = array('barber', 'beauty', 'massage'); $serviceArray = array( - 'service_type' => $_POST['service_type'], + 'service_type' => (isset($_POST['service_type']) && in_array($_POST['service_type'], $allowedServiceTypes, true)) ? $_POST['service_type'] : 'beauty', 'service_category_no' => $_POST['service_category_no'], 'service_category_en' => $_POST['service_category_en'], 'service_category_hu' => $_POST['service_category_hu'], @@ -320,8 +321,10 @@ class Admin extends CI_Controller { } if(isset($_POST['updateService'])){ + $allowedServiceTypes = array('barber', 'beauty', 'massage'); + $existingService = $this->Service_model->getServiceById($_POST['service_id']); $serviceArray = array( - 'service_type' => $_POST['service_type'], + 'service_type' => (isset($_POST['service_type']) && in_array($_POST['service_type'], $allowedServiceTypes, true)) ? $_POST['service_type'] : ($existingService ? $existingService->service_type : 'beauty'), 'service_category_no' => $_POST['service_category_no'], 'service_category_en' => $_POST['service_category_en'], 'service_category_hu' => $_POST['service_category_hu'], @@ -542,6 +545,8 @@ class Admin extends CI_Controller { $data['serviceCategories'] = $this->Service_model->getAllServiceCategories(); $data['selectedItem'] = $this->Service_model->getWorkerById($worker_id); + // Verticals are derived from the worker's service category, not stored. + $data['workerVerticals'] = $this->Service_model->getVerticalsForWorker($worker_id); $this->load->view('admin/update-worker', $data); } else{ @@ -597,11 +602,12 @@ class Admin extends CI_Controller { } + // is_beauty / is_barber are no longer submitted by the form - the + // vertical is derived from service_category_id. Service_model + // keeps the legacy columns consistent on write. $workerArray = array( 'worker_name' => $_POST['worker_name'], 'worker_info' => $_POST['worker_info'], - 'is_beauty' => $_POST['is_beauty'], - 'is_barber' => $_POST['is_barber'], 'worker_profile_img' => $data['worker_img'], 'service_category_id' => $_POST['service_category_id'], 'is_active' => $_POST['is_active'] @@ -615,8 +621,6 @@ class Admin extends CI_Controller { $workerArray = array( 'worker_name' => $_POST['worker_name'], 'worker_info' => $_POST['worker_info'], - 'is_beauty' => $_POST['is_beauty'], - 'is_barber' => $_POST['is_barber'], 'service_category_id' => $_POST['service_category_id'], 'is_active' => $_POST['is_active'], 'google_calendar_id' => isset($_POST['google_calendar_id']) ? $_POST['google_calendar_id'] : '', diff --git a/application/controllers/Pages.php b/application/controllers/Pages.php index 82a0c43..cc8d946 100755 --- a/application/controllers/Pages.php +++ b/application/controllers/Pages.php @@ -49,6 +49,31 @@ class Pages extends CI_Controller { $this->load->view('pages/beauty', $data); } + /** + * Generic landing page for any registered vertical. + * + * barber() and beauty() above are kept as-is so their rendered HTML does + * not change; new verticals route here instead of gaining a method each. + */ + public function vertical($lang, $slug){ + $this->load->helper('url'); + $this->load->model('User_model'); + $this->load->model('Service_model'); + + $vertical = vertical_get($slug); + if(!$vertical){ + show_404(); + return; + } + + $data['pageTitle'] = ''; + $data['selectedLang'] = vertical_lang($lang); + $data['vertical'] = $vertical; + $data['services'] = $this->Service_model->getAllServiceByServiceType($vertical['slug'], $data['selectedLang']); + + $this->load->view($vertical['view_landing'], $data); + } + public function booking($lang, $subpage){ $this->load->helper('url'); $this->load->model('User_model'); @@ -58,19 +83,20 @@ class Pages extends CI_Controller { $data['selectedLang'] = $lang; $data['subpage'] = $subpage; + // Whitelist the vertical through the registry. An unknown slug used to + // fall through the switch default to a bare redirect; now anything not + // registered and enabled is rejected the same way, in one place. + $vertical = vertical_get($subpage); + if(!$vertical){ + header('Location:'.SITEURL); + return; + } + + $data['vertical'] = $vertical; $data['services'] = $this->Service_model->getAllServiceByServiceType($subpage, $lang); $data['workers'] = $this->Service_model->getActiveWorkers($subpage); - switch($subpage){ - case 'barber': - $this->load->view('pages/barber-booking', $data); - break; - case 'beauty': - $this->load->view('pages/beauty-booking', $data); - break; - default: - header('Location:'.SITEURL); - } + $this->load->view($vertical['view_booking'], $data); } public function booking_finished($lang, $subpage){ @@ -80,16 +106,16 @@ class Pages extends CI_Controller { $data['pageTitle'] = ''; $data['selectedLang'] = $lang; - switch($subpage){ - case 'barber': - $this->load->view('pages/barber-booking-finished', $data); - break; - case 'beauty': - $this->load->view('pages/beauty-booking-finished', $data); - break; - default: + $vertical = vertical_get($subpage); + if(!$vertical){ header('Location:'.SITEURL); + return; } + + $data['vertical'] = $vertical; + $data['subpage'] = $subpage; + + $this->load->view($vertical['view_finished'], $data); } public function email_verification(){ @@ -284,24 +310,25 @@ class Pages extends CI_Controller { private function _booking_error($errorCode, $statusCode = 409){ $this->load->helper('url'); - $lang = isset($_POST['lang']) && in_array($_POST['lang'], array('no', 'en', 'hu')) ? $_POST['lang'] : 'en'; - $subpage = isset($_POST['subpage']) && $_POST['subpage'] != '' ? $_POST['subpage'] : 'barber'; + $lang = vertical_lang(isset($_POST['lang']) ? $_POST['lang'] : ''); + + // vertical_get() doubles as the whitelist for the posted subpage: + // anything unregistered falls back to barber, as before. + $vertical = vertical_get(isset($_POST['subpage']) ? $_POST['subpage'] : ''); + if(!$vertical){ + $vertical = vertical_get('barber'); + } $data = array( 'pageTitle' => '', 'selectedLang' => $lang, - 'subpage' => $subpage, + 'subpage' => $vertical['slug'], + 'vertical' => $vertical, 'bookingErrorCode' => $errorCode ); $this->output->set_status_header($statusCode); - - if($subpage == 'beauty'){ - $this->load->view('pages/beauty-booking-error', $data); - } - else{ - $this->load->view('pages/barber-booking-error', $data); - } + $this->load->view($vertical['view_error'], $data); } public function booking_process(){ @@ -379,8 +406,40 @@ class Pages extends CI_Controller { $bookingDate = $bookingArray['booking_date']; $bookingTime = $bookingArray['booking_start_time']; - $weekday = date('w', strtotime($bookingDate)); - $schedule = $this->Service_model->getWorkerScheduleByDay($bookingArray['worker_id'], $weekday); + + // Category match: the worker must actually perform EVERY selected service. + // Mirrors the guard in manage_booking_process(); server-side equivalent of the + // client-side mutual exclusion, so a crafted or replayed POST cannot book a + // worker for another vertical's services. + $submittedWorker = $this->Service_model->getWorkerById($bookingArray['worker_id']); + if (!$submittedWorker) { + $this->_booking_error('worker_unavailable', 403); + return; + } + if (empty($selectedServiceArray)) { + $this->_booking_error('no_service', 400); + return; + } + foreach ($selectedServiceArray as $selectedServiceId) { + $selectedService = $this->Service_model->getServiceById($selectedServiceId); + if (!$selectedService || $selectedService->service_category_id != $submittedWorker->service_category_id) { + $this->_booking_error('category_mismatch', 403); + return; + } + } + + // Single-service verticals: the client disables the other checkboxes, + // but that is a UI affordance only. The vertical is derived from the + // services themselves rather than $_POST['subpage'], which is + // client-supplied. + $firstSelectedService = $this->Service_model->getServiceById($selectedServiceArray[0]); + $bookingVertical = $firstSelectedService ? vertical_get($firstSelectedService->service_type) : NULL; + if (!empty($bookingVertical['single_service_booking']) && count($selectedServiceArray) > 1) { + $this->_booking_error('single_service_only', 400); + return; + } + + $schedule = $this->Service_model->getWorkerScheduleForDate($bookingArray['worker_id'], $bookingDate); if (!$schedule) { $this->_booking_error('worker_unavailable', 403); @@ -392,7 +451,7 @@ class Pages extends CI_Controller { return; } - if (!$this->Service_model->isWorkerAvailableThisWeek($bookingArray['worker_id'], $bookingDate)) { + if ($schedule->source === 'schedule' && !$this->Service_model->isWorkerAvailableThisWeek($bookingArray['worker_id'], $bookingDate)) { $this->_booking_error('alternate_week', 403); return; } @@ -755,23 +814,37 @@ class Pages extends CI_Controller { return; } - // Determine booking type from services + // Determine booking type from services. Scan until one resolves rather + // than trusting services[0]: if that single service has since been + // hard-deleted, the guest's manage page would silently render with the + // wrong vertical's branding. $serviceIds = unserialize($booking->service_ids); $subpage = 'barber'; - if(is_array($serviceIds) && !empty($serviceIds)){ - $firstService = $this->Service_model->getServiceById($serviceIds[0]); - if($firstService){ - $subpage = $firstService->service_type; + if(is_array($serviceIds)){ + foreach($serviceIds as $serviceIdItem){ + $resolvedService = $this->Service_model->getServiceById($serviceIdItem); + if($resolvedService && $resolvedService->service_type !== ''){ + $subpage = $resolvedService->service_type; + break; + } } } $now = new DateTime('now', new DateTimeZone('Europe/Oslo')); $appointmentDT = new DateTime($booking->booking_date.' '.$booking->booking_start_time, new DateTimeZone('Europe/Oslo')); + // A booking's vertical is derived from its services, so an unregistered + // or stale service_type must still render something: fall back to barber. + $vertical = vertical_get($subpage); + if(!$vertical){ + $vertical = vertical_get('barber'); + } + $data['token'] = $token; $data['booking'] = $booking; $data['selectedLang'] = $lang; - $data['subpage'] = $subpage; + $data['subpage'] = $vertical['slug']; + $data['vertical'] = $vertical; $data['pageTitle'] = ''; $data['status'] = 'ok'; @@ -863,8 +936,7 @@ class Pages extends CI_Controller { } // Schedule check - $weekday = date('w', strtotime($newBookingDate)); - $schedule = $this->Service_model->getWorkerScheduleByDay($newWorkerId, $weekday); + $schedule = $this->Service_model->getWorkerScheduleForDate($newWorkerId, $newBookingDate); if(!$schedule){ $this->output->set_status_header(403)->set_content_type('application/json') ->set_output(json_encode(['error' => 'Worker is not available on this day.'])); diff --git a/application/helpers/vertical_helper.php b/application/helpers/vertical_helper.php new file mode 100644 index 0000000..8590bbe --- /dev/null +++ b/application/helpers/vertical_helper.php @@ -0,0 +1,239 @@ +config->load('verticals', FALSE, TRUE); + $verticals = $CI->config->item('verticals'); + + return is_array($verticals) ? $verticals : array(); + } +} + +if ( ! function_exists('vertical_get')) +{ + /** + * One vertical by slug, or NULL when the slug is unknown or disabled. + * + * Use this as the whitelist for any user-supplied vertical value. + * + * @param string $slug + * @return array|null + */ + function vertical_get($slug) + { + if ( ! is_string($slug) || $slug === '') + { + return NULL; + } + + $verticals = vertical_all(); + + if ( ! isset($verticals[$slug])) + { + return NULL; + } + + if (empty($verticals[$slug]['enabled'])) + { + return NULL; + } + + return $verticals[$slug]; + } +} + +if ( ! function_exists('vertical_exists')) +{ + /** + * @param string $slug + * @return bool + */ + function vertical_exists($slug) + { + return vertical_get($slug) !== NULL; + } +} + +if ( ! function_exists('vertical_slugs')) +{ + /** + * Slugs of every enabled vertical. Suitable for in_array() checks and for + * building the admin service_type dropdown. + * + * @return array + */ + function vertical_slugs() + { + $slugs = array(); + + foreach (vertical_all() as $slug => $vertical) + { + if ( ! empty($vertical['enabled'])) + { + $slugs[] = $slug; + } + } + + return $slugs; + } +} + +if ( ! function_exists('vertical_public')) +{ + /** + * Enabled verticals flagged public, ordered by 'sort'. This is what the + * home page tiles iterate over — flipping 'public' is the launch switch. + * + * @return array + */ + function vertical_public() + { + $public = array(); + + foreach (vertical_all() as $slug => $vertical) + { + if ( ! empty($vertical['enabled']) && ! empty($vertical['public'])) + { + $public[$slug] = $vertical; + } + } + + uasort($public, 'vertical_sort_compare'); + + return $public; + } +} + +if ( ! function_exists('vertical_sort_compare')) +{ + /** + * uasort() callback. Named rather than a closure so this helper stays + * compatible with the PHP version the live host runs. + * + * @param array $a + * @param array $b + * @return int + */ + function vertical_sort_compare($a, $b) + { + $sa = isset($a['sort']) ? (int) $a['sort'] : 0; + $sb = isset($b['sort']) ? (int) $b['sort'] : 0; + + if ($sa === $sb) + { + return 0; + } + + return ($sa < $sb) ? -1 : 1; + } +} + +if ( ! function_exists('vertical_label')) +{ + /** + * Display label for a vertical in the given language, falling back to + * English and then to the uppercased slug. + * + * @param array $vertical + * @param string $lang + * @return string + */ + function vertical_label($vertical, $lang) + { + if (isset($vertical['label'][$lang])) + { + return $vertical['label'][$lang]; + } + + if (isset($vertical['label']['en'])) + { + return $vertical['label']['en']; + } + + return isset($vertical['slug']) ? strtoupper($vertical['slug']) : ''; + } +} + +if ( ! function_exists('vertical_lang')) +{ + /** + * Whitelist a language segment. The site serves no/en/hu; anything else + * falls back to English, matching the existing per-view $langArray checks. + * + * @param string $lang + * @return string + */ + function vertical_lang($lang) + { + $allowed = array('no', 'en', 'hu'); + + return (is_string($lang) && in_array($lang, $allowed, TRUE)) ? $lang : 'en'; + } +} + +if ( ! function_exists('vertical_service_label')) +{ + /** + * Display label for a service in a list that is ALREADY grouped under its + * category heading. + * + * services.service_name has to stay self-describing - the confirmation email + * and the admin lists render it with no category around it. But in the + * booking form, the manage-booking form and the landing-page price list the + * category heading sits directly above the row, so repeating it makes every + * row wrap and the list read as cramped: + * + * [Studio Beve Signature - individuelt tilpasset massasje] + * Studio Beve Signature - individuelt tilpasset massasje - 50 min + * + * Opt in per vertical via 'strip_category_prefix' so barber and beauty keep + * rendering exactly as before. + * + * @param array $vertical registry row (may be NULL or missing the key) + * @param object $serviceItem needs ->service_name and ->service_category + * @return string + */ + function vertical_service_label($vertical, $serviceItem) + { + $name = isset($serviceItem->service_name) ? $serviceItem->service_name : ''; + + if (empty($vertical['strip_category_prefix'])) + { + return $name; + } + + $category = isset($serviceItem->service_category) ? $serviceItem->service_category : ''; + + if ($category === '' OR strpos($name, $category) !== 0) + { + return $name; + } + + // The /u modifier matters: the separator is an en dash, and a byte-wise + // trim() would only mangle it. + $stripped = substr($name, strlen($category)); + $stripped = preg_replace('/^[\s\-–—]+/u', '', $stripped); + $stripped = trim($stripped); + + return ($stripped !== '') ? $stripped : $name; + } +} diff --git a/application/models/Service_model.php b/application/models/Service_model.php index 62127dc..639fc1e 100755 --- a/application/models/Service_model.php +++ b/application/models/Service_model.php @@ -63,8 +63,70 @@ class Service_model extends CI_Model { $this->db->update('service_categories', $serviceCategoryArray); } + /** + * The verticals (service_type values) a service category covers. + * + * This is the single source of truth for "what does this worker do". + * + * @param int $service_category_id + * @return array e.g. array('barber', 'beauty') + */ + public function getVerticalsForCategory($service_category_id){ + $this->load->database(); + $query = $this->db->query(' + SELECT DISTINCT service_type + FROM services + WHERE service_category_id = ? + AND is_deleted != "1" + ORDER BY service_type ASC;', array($service_category_id)); + + $types = array(); + foreach($query->result() as $row){ + $types[] = $row->service_type; + } + return $types; + } + + /** + * The verticals a given worker covers, derived from their category. + * + * @param int $worker_id + * @return array + */ + public function getVerticalsForWorker($worker_id){ + $this->load->database(); + $worker = $this->getWorkerById($worker_id); + + if(!$worker){ + return array(); + } + + return $this->getVerticalsForCategory($worker->service_category_id); + } + + /** + * Keep the legacy is_beauty / is_barber columns consistent with the + * category. NOTHING reads them any more - the vertical is derived (see + * getActiveWorkers). They are still NOT NULL, and writing a correct value + * means a code rollback cannot strand a newly created worker as invisible. + * Derived here rather than trusted from $_POST so the two cannot drift. + * + * @param array $workerArray by reference; keys are added + * @param int $service_category_id + * @return void + */ + private function _applyLegacyVerticalFlags(&$workerArray, $service_category_id){ + $verticals = $this->getVerticalsForCategory($service_category_id); + + $workerArray['is_beauty'] = in_array('beauty', $verticals, TRUE) ? 1 : 0; + $workerArray['is_barber'] = in_array('barber', $verticals, TRUE) ? 1 : 0; + } + public function createWorker($workerArray){ $this->load->database(); + + $this->_applyLegacyVerticalFlags($workerArray, $workerArray['service_category_id']); + $query = $this->db->query('INSERT INTO workers ( worker_name, worker_profile_img, @@ -84,6 +146,13 @@ class Service_model extends CI_Model { public function updateWorker($worker_id, $workerArray){ $this->load->database(); + + // Re-derive the legacy flags whenever the category is being written, + // so moving a worker between categories cannot leave them stale. + if(isset($workerArray['service_category_id'])){ + $this->_applyLegacyVerticalFlags($workerArray, $workerArray['service_category_id']); + } + $this->db->where('worker_id', $worker_id); $this->db->update('workers', $workerArray); } @@ -206,7 +275,28 @@ class Service_model extends CI_Model { public function getAllWorkers(){ $this->load->database(); - $query = $this->db->query('SELECT * FROM workers JOIN service_categories ON workers.service_category_id = service_categories.service_category_id WHERE workers.is_deleted != "1" ORDER BY worker_name ASC;'); + + // LEFT JOIN, not INNER: with an inner join a worker whose category row + // is missing or soft-deleted vanishes from the admin list entirely + // rather than showing up as broken. Silent disappearance is worse. + // + // derived_verticals is computed here (one subquery, not N+1) so the + // list can show what a worker actually does without consulting the + // legacy is_barber / is_beauty columns. + $query = $this->db->query(' + SELECT w.*, + sc.serv_cat_name, + sc.serv_cat_slug, + (SELECT GROUP_CONCAT(DISTINCT s.service_type ORDER BY s.service_type SEPARATOR ", ") + FROM services s + WHERE s.service_category_id = w.service_category_id + AND s.is_deleted != "1") AS derived_verticals + FROM workers w + LEFT JOIN service_categories sc + ON sc.service_category_id = w.service_category_id + WHERE w.is_deleted != "1" + ORDER BY w.worker_name ASC;'); + return $query->result(); } @@ -216,21 +306,52 @@ class Service_model extends CI_Model { return $query->result(); } + /** + * Active workers, optionally restricted to one vertical. + * + * The vertical is DERIVED from the data that actually governs booking: a + * worker performs a vertical iff at least one live service of that + * service_type shares the worker's service_category_id. + * + * This replaces the old workers.is_barber / workers.is_beauty flags, which + * were a hand-maintained cache of exactly this fact and free to drift from + * it. Verified against production data before the switch: the derived set + * reproduced the stored flags exactly, for every worker, in both verticals. + * A new vertical therefore needs no schema change and no new flag column. + * + * Also removes the last string-concatenated WHERE fragment in this model - + * the obvious "fix" for a third vertical was ' AND is_'.$subpage.' = "1"', + * which would have turned this into an injection sink reachable from an + * unauthenticated POST. + * + * @param string $subpage vertical slug, or '' for every active worker + * @return array + */ public function getActiveWorkers($subpage = ''){ $this->load->database(); - $subpageString = ''; - if($subpage == 'beauty'){ - $subpageString = ' AND is_beauty = "1"'; - } - else if($subpage == 'barber'){ - $subpageString = ' AND is_barber = "1"'; - } - else{ - $subpageString = ''; + // No-argument callers (Admin bookings filter, booking calendar) want + // every active worker regardless of vertical. Preserve that exactly. + if($subpage === '' || $subpage === NULL){ + $query = $this->db->query('SELECT * FROM workers WHERE is_active = "1" AND is_deleted != "1" ORDER BY worker_name ASC;'); + return $query->result(); } - $query = $this->db->query('SELECT * FROM workers WHERE is_active = "1" '.$subpageString.' AND is_deleted != "1" ORDER BY worker_name ASC;'); + // EXISTS rather than JOIN + DISTINCT: a worker with several matching + // services must still appear once, and workers.worker_info is TEXT, + // which DISTINCT would have to de-duplicate needlessly. + $query = $this->db->query(' + SELECT * FROM workers w + WHERE w.is_active = "1" + AND w.is_deleted != "1" + AND EXISTS ( + SELECT 1 FROM services s + WHERE s.service_category_id = w.service_category_id + AND s.service_type = ? + AND s.is_deleted != "1" + ) + ORDER BY w.worker_name ASC;', array($subpage)); + return $query->result(); } @@ -255,6 +376,20 @@ class Service_model extends CI_Model { $services = array(); foreach($servicesArray as $servicesArrayItem){ $selectedService = $this->Service_model->getServiceById($servicesArrayItem); + + // A hard-deleted service leaves older bookings pointing at a + // row that no longer exists. Render a visible placeholder + // rather than dereferencing null and blanking the row. + if(!$selectedService){ + $services[] = (object)array( + 'service_type' => '', + 'service_name' => '[torolt szolgaltatas #'.$servicesArrayItem.']', + 'service_price' => 0, + 'service_time' => '00:00:00' + ); + continue; + } + $services[] = (object)array( 'service_type' => $selectedService->service_type, 'service_name' => $selectedService->service_name_hu, @@ -299,6 +434,20 @@ class Service_model extends CI_Model { $services = array(); foreach($servicesArray as $servicesArrayItem){ $selectedService = $this->Service_model->getServiceById($servicesArrayItem); + + // A hard-deleted service leaves older bookings pointing at a + // row that no longer exists. Render a visible placeholder + // rather than dereferencing null and blanking the row. + if(!$selectedService){ + $services[] = (object)array( + 'service_type' => '', + 'service_name' => '[torolt szolgaltatas #'.$servicesArrayItem.']', + 'service_price' => 0, + 'service_time' => '00:00:00' + ); + continue; + } + $services[] = (object)array( 'service_type' => $selectedService->service_type, 'service_name' => $selectedService->service_name_hu, @@ -343,6 +492,20 @@ class Service_model extends CI_Model { $services = array(); foreach($servicesArray as $servicesArrayItem){ $selectedService = $this->Service_model->getServiceById($servicesArrayItem); + + // A hard-deleted service leaves older bookings pointing at a + // row that no longer exists. Render a visible placeholder + // rather than dereferencing null and blanking the row. + if(!$selectedService){ + $services[] = (object)array( + 'service_type' => '', + 'service_name' => '[torolt szolgaltatas #'.$servicesArrayItem.']', + 'service_price' => 0, + 'service_time' => '00:00:00' + ); + continue; + } + $services[] = (object)array( 'service_type' => $selectedService->service_type, 'service_name' => $selectedService->service_name_hu, @@ -796,6 +959,68 @@ class Service_model extends CI_Model { return $query->row(); // null if no row } + /** + * Date-aware schedule lookup. Mirrors the resolution order inside + * getAvailableTimes() exactly: an override for the date wins outright, + * otherwise fall back to worker_schedule honouring is_alternate_week. + * + * getWorkerScheduleByDay() above ignores both overrides and alternate + * weeks, so using it as a booking guard rejects slots the availability + * UI legitimately offered. Prefer this method for guards. + * + * @return object|null ->start_time, ->end_time, ->source ('override'|'schedule') + */ + public function getWorkerScheduleForDate($worker_id, $selectedDate) { + $this->load->database(); + + $overrideQuery = $this->db->query(" + SELECT * FROM worker_schedule_overrides + WHERE worker_id = ? AND date = ? + LIMIT 1 + ", array($worker_id, $selectedDate)); + + if ($overrideQuery->num_rows() > 0) { + $override = $overrideQuery->row(); + + if ($override->is_day_off) { + return null; + } + if ($override->start_time === null || $override->end_time === null) { + return null; + } + + return (object) array( + 'start_time' => $override->start_time, + 'end_time' => $override->end_time, + 'is_alternate_week' => 0, + 'source' => 'override', + ); + } + + $weekday = date('w', strtotime($selectedDate)); + $weekNumber = date('W', strtotime($selectedDate)); + $isOddWeek = $weekNumber % 2 !== 0; + + $scheduleQuery = $this->db->query(" + SELECT * FROM worker_schedule + WHERE worker_id = ? + AND weekday = ? + AND ( + is_alternate_week = 0 + OR (is_alternate_week = 1 AND " . ($isOddWeek ? "1" : "0") . ") + ) + LIMIT 1 + ", array($worker_id, $weekday)); + + if ($scheduleQuery->num_rows() == 0) { + return null; + } + + $schedule = $scheduleQuery->row(); + $schedule->source = 'schedule'; + return $schedule; + } + public function isEvenWeek($date) { return ((int)date('W', strtotime($date)) % 2) === 0; } diff --git a/application/views/admin/includes/add-service-form.php b/application/views/admin/includes/add-service-form.php index 4907fef..ab8224b 100755 --- a/application/views/admin/includes/add-service-form.php +++ b/application/views/admin/includes/add-service-form.php @@ -7,6 +7,7 @@
@@ -59,7 +60,7 @@ service_category_id.'" selected>'.$serviceCategoryItem->serv_cat_name.''; + echo ''; } } ?> diff --git a/application/views/admin/includes/add-worker-form.php b/application/views/admin/includes/add-worker-form.php index 129e5e1..e4c13f3 100755 --- a/application/views/admin/includes/add-worker-form.php +++ b/application/views/admin/includes/add-worker-form.php @@ -22,18 +22,11 @@
- - -
-
- - + +
+ A dolgozó vertikáljait (barber / beauty / massage) az alább választott + szolgáltatás kategória határozza meg - nem kell külön beállítani. +
@@ -41,7 +34,7 @@ service_category_id.'" selected>'.$serviceCategoryItem->serv_cat_name.''; + echo ''; } } ?> diff --git a/application/views/admin/includes/update-service-form.php b/application/views/admin/includes/update-service-form.php index 551e4cf..11a8870 100755 --- a/application/views/admin/includes/update-service-form.php +++ b/application/views/admin/includes/update-service-form.php @@ -7,6 +7,7 @@
diff --git a/application/views/admin/includes/update-worker-form.php b/application/views/admin/includes/update-worker-form.php index df195fe..3a8b4a3 100755 --- a/application/views/admin/includes/update-worker-form.php +++ b/application/views/admin/includes/update-worker-form.php @@ -18,18 +18,18 @@
- - -
-
- - + +
+ nincs szolgáltatás ebben a kategóriában'; + ?> +
+ A szolgáltatás kategóriából származtatva - a kategória módosításával változik. +
+
diff --git a/application/views/admin/includes/workers-form.php b/application/views/admin/includes/workers-form.php index 4a76ccd..f196e32 100755 --- a/application/views/admin/includes/workers-form.php +++ b/application/views/admin/includes/workers-form.php @@ -11,8 +11,7 @@ Profilkép Név Leírás - Beauty - Barber + Vertikálok Szerviz kategória Aktív @@ -30,8 +29,7 @@ worker_name, ENT_QUOTES, 'UTF-8');?> worker_info, ENT_QUOTES, 'UTF-8');?> - is_beauty?'igen':'nem';?> - is_barber?'igen':'nem';?> + derived_verticals !== null && $resultItem->derived_verticals !== '' ? htmlspecialchars($resultItem->derived_verticals, ENT_QUOTES, 'UTF-8') : 'nincs szolgáltatás';?> serv_cat_name, ENT_QUOTES, 'UTF-8');?> is_active?'igen':'nem';?> diff --git a/application/views/includes/vertical-footer.php b/application/views/includes/vertical-footer.php new file mode 100644 index 0000000..07633c4 --- /dev/null +++ b/application/views/includes/vertical-footer.php @@ -0,0 +1,60 @@ + copies per vertical file. The address, + * org number and map link are salon-wide; only the social accounts and the map + * image are per-vertical, and both come from the registry. + */ + +$labels = array( + 'en' => array( + 'orgnr' => 'Organisation number', + 'vacancies' => 'Open positions', + 'copyright' => '© Studio Beve - All rights reserved.', + ), + 'no' => array( + 'orgnr' => 'Organisasjonsnummer', + 'vacancies' => 'Ledige stillinger', + 'copyright' => '© Studio Beve - All rights reserved.', + ), + 'hu' => array( + 'orgnr' => 'Cégjegyzékszám', + 'vacancies' => 'Állásajánlatok', + 'copyright' => '© Studio Beve - All rights reserved.', + ), +); + +$lang = vertical_lang(isset($selectedLang) ? $selectedLang : 'en'); +$t = $labels[$lang]; + +$mapUrl = 'https://www.google.com/maps/place/Studio+Beve/@63.4370671,10.4348909,19z/data=!3m1!4b1!4m6!3m5!1s0x466d31f2b1166d2b:0x76228adf28216c74!8m2!3d63.4370671!4d10.4355346!16s%2Fg%2F11vlvkh6w4?entry=ttu'; +?> + + diff --git a/application/views/includes/vertical-head.php b/application/views/includes/vertical-head.php new file mode 100644 index 0000000..665db75 --- /dev/null +++ b/application/views/includes/vertical-head.php @@ -0,0 +1,50 @@ + for any vertical. + * + * Expects $vertical (a row from application/config/verticals.php). + * Replaces the per-vertical barber-head.php / beauty-head.php, which differ + * only in , og:site_name, og:url, meta description and the stylesheet. + */ +$this->load->helper('url'); +?> +<head> + <!-- Google Tag Manager --> + <script>(function(w,d,s,l,i){w[l]=w[l]||[];w[l].push({'gtm.start': + new Date().getTime(),event:'gtm.js'});var f=d.getElementsByTagName(s)[0], + j=d.createElement(s),dl=l!='dataLayer'?'&l='+l:'';j.async=true;j.src= + 'https://www.googletagmanager.com/gtm.js?id='+i+dl;f.parentNode.insertBefore(j,f); + })(window,document,'script','dataLayer','GTM-TWRHH7DN');</script> + <!-- End Google Tag Manager --> + + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1, user-scalable=no" /> + <title><?php echo htmlspecialchars($vertical['title'], ENT_QUOTES, 'UTF-8');?> + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/application/views/includes/vertical-header.php b/application/views/includes/vertical-header.php new file mode 100644 index 0000000..838c5e7 --- /dev/null +++ b/application/views/includes/vertical-header.php @@ -0,0 +1,59 @@ + - one per language plus an unlabelled else-fallback that was + * reached whenever $selectedLang was not en/hu/no (and which referenced + * $currentPageUrlArray before it was defined). Here there is one nav and one + * label table, so the fallback cannot drift from the real branches. + */ + +$labels = array( + 'en' => array( + 'home' => 'Home', 'about' => 'About US', 'treatments' => 'Treatments', + 'prices' => 'Prices', 'hours' => 'Opening Hours', 'contact' => 'Contact', + 'book' => 'BOOK TIME', + ), + 'no' => array( + 'home' => 'Forside', 'about' => 'Om oss', 'treatments' => 'Behandlinger', + 'prices' => 'Priser', 'hours' => 'Åpningstider', 'contact' => 'Kontakt', + 'book' => 'BESTILL TIME', + ), + 'hu' => array( + 'home' => 'Kezdőlap', 'about' => 'Rólunk', 'treatments' => 'Szolgáltatások', + 'prices' => 'Árlista', 'hours' => 'Nyitvatartás', 'contact' => 'Kapcsolat', + 'book' => 'IDŐPONTFOGLALÁS', + ), +); + +$lang = vertical_lang(isset($selectedLang) ? $selectedLang : 'en'); +$t = $labels[$lang]; + +// Language switcher: rebuild the current URL with a different language segment. +// Segment 1 is the language, 2 and 3 are the page path. +$currentPageUrlArray = explode('/', $_SERVER['REQUEST_URI']); +$currentPageUrlArray += array(1 => '', 2 => '', 3 => ''); +?> + + diff --git a/application/views/includes/vertical-skeleton-bottom.php b/application/views/includes/vertical-skeleton-bottom.php new file mode 100644 index 0000000..1472753 --- /dev/null +++ b/application/views/includes/vertical-skeleton-bottom.php @@ -0,0 +1,15 @@ + + + +
+ + + + + + + + + diff --git a/application/views/includes/vertical-skeleton-top.php b/application/views/includes/vertical-skeleton-top.php new file mode 100644 index 0000000..cff522d --- /dev/null +++ b/application/views/includes/vertical-skeleton-top.php @@ -0,0 +1,21 @@ + + + + + + + + + +
+ + + +
+ +
diff --git a/application/views/pages/includes/barber-form-en.php b/application/views/pages/includes/barber-form-en.php index 75d9383..a1f2aae 100755 --- a/application/views/pages/includes/barber-form-en.php +++ b/application/views/pages/includes/barber-form-en.php @@ -43,7 +43,7 @@

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.

diff --git a/application/views/pages/includes/barber-form-hu.php b/application/views/pages/includes/barber-form-hu.php index 05749b9..230a278 100755 --- a/application/views/pages/includes/barber-form-hu.php +++ b/application/views/pages/includes/barber-form-hu.php @@ -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.

- 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.

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. diff --git a/application/views/pages/includes/barber-form-no.php b/application/views/pages/includes/barber-form-no.php index e3d3940..1c5cf5f 100755 --- a/application/views/pages/includes/barber-form-no.php +++ b/application/views/pages/includes/barber-form-no.php @@ -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.

- 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.

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. diff --git a/application/views/pages/includes/beauty-form-en.php b/application/views/pages/includes/beauty-form-en.php index abeffa2..e54437e 100755 --- a/application/views/pages/includes/beauty-form-en.php +++ b/application/views/pages/includes/beauty-form-en.php @@ -41,7 +41,7 @@

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.

diff --git a/application/views/pages/includes/beauty-form-hu.php b/application/views/pages/includes/beauty-form-hu.php index 72ffe3a..c6a8171 100755 --- a/application/views/pages/includes/beauty-form-hu.php +++ b/application/views/pages/includes/beauty-form-hu.php @@ -39,7 +39,7 @@

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.

diff --git a/application/views/pages/includes/beauty-form-no.php b/application/views/pages/includes/beauty-form-no.php index 815f415..ebf8363 100755 --- a/application/views/pages/includes/beauty-form-no.php +++ b/application/views/pages/includes/beauty-form-no.php @@ -40,7 +40,7 @@

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.

diff --git a/application/views/pages/includes/booking-error-form-en.php b/application/views/pages/includes/booking-error-form-en.php index f64d4fe..55f2e99 100755 --- a/application/views/pages/includes/booking-error-form-en.php +++ b/application/views/pages/includes/booking-error-form-en.php @@ -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.', ); diff --git a/application/views/pages/includes/booking-error-form-hu.php b/application/views/pages/includes/booking-error-form-hu.php index 715b4bf..c79cc2e 100755 --- a/application/views/pages/includes/booking-error-form-hu.php +++ b/application/views/pages/includes/booking-error-form-hu.php @@ -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.', ); diff --git a/application/views/pages/includes/booking-error-form-no.php b/application/views/pages/includes/booking-error-form-no.php index f7cd70c..bd38e73 100755 --- a/application/views/pages/includes/booking-error-form-no.php +++ b/application/views/pages/includes/booking-error-form-no.php @@ -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.', ); diff --git a/application/views/pages/includes/booking-form-en.php b/application/views/pages/includes/booking-form-en.php index 9a82f9a..f8fe150 100755 --- a/application/views/pages/includes/booking-form-en.php +++ b/application/views/pages/includes/booking-form-en.php @@ -95,8 +95,8 @@ foreach ($groups[$label] as $serviceItem) { ?>

-
- service_name; ?> +
+
@@ -236,6 +236,26 @@ } }); + + /* 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'); + } + }); + + $('.service').click(function(){ var totalPrice = 0; var selectedServices = '
'; @@ -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 + '
'+selectedNameElement+'
'+selectedPriceElement+' kr
'; 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('
No staff available for this service.
'); + setWorker(''); + } }); } diff --git a/application/views/pages/includes/booking-form-hu.php b/application/views/pages/includes/booking-form-hu.php index b4e2d84..381f258 100755 --- a/application/views/pages/includes/booking-form-hu.php +++ b/application/views/pages/includes/booking-form-hu.php @@ -95,8 +95,8 @@ foreach ($groups[$label] as $serviceItem) { ?>
-
- service_name; ?> +
+
@@ -235,6 +235,26 @@ } }); + + /* 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'); + } + }); + + $('.service').click(function(){ var totalPrice = 0; var selectedServices = '
'; @@ -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 + '
'+selectedNameElement+'
'+selectedPriceElement+' kr
'; 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('
Nincs elérhető munkatárs ehhez a szolgáltatáshoz.
'); + setWorker(''); + } }); } diff --git a/application/views/pages/includes/booking-form-no.php b/application/views/pages/includes/booking-form-no.php index c236b7c..2b2ba1b 100755 --- a/application/views/pages/includes/booking-form-no.php +++ b/application/views/pages/includes/booking-form-no.php @@ -95,8 +95,8 @@ if (is_array($services) && count($services) > 0) { foreach ($groups[$label] as $serviceItem) { ?>
-
- service_name; ?> +
+
@@ -235,6 +235,26 @@ if (is_array($services) && count($services) > 0) { } }); + + /* 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'); + } + }); + + $('.service').click(function(){ var totalPrice = 0; var selectedServices = '
'; @@ -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 + '
'+selectedNameElement+'
'+selectedPriceElement+' kr
'; 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('
Ingen ledige medarbeidere for denne tjenesten.
'); + setWorker(''); + } }); } diff --git a/application/views/pages/includes/home-form.php b/application/views/pages/includes/home-form.php index e4e025d..968a23e 100755 --- a/application/views/pages/includes/home-form.php +++ b/application/views/pages/includes/home-form.php @@ -1,30 +1,25 @@
-
+ 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): +?> + - +
- - - - - - diff --git a/application/views/pages/includes/massage-form-en.php b/application/views/pages/includes/massage-form-en.php new file mode 100644 index 0000000..87d7e9a --- /dev/null +++ b/application/views/pages/includes/massage-form-en.php @@ -0,0 +1,293 @@ + + + +
+ +
+ + +
+
+
+
+ +

About us

+
+ + .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. */ ?> +
+
+

+ 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. +

+

+ Quality time, maximum attention, the most professional tools, together with + professionalism and expertise, stand for the basis of our philosophy and + services. +

+

+ 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. +

+

+ 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. +

+

+ 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. +

+
+
+ +

Evelin Bence
Owner, Studio Beve

+
+
+ +
+
+ +

VINCENT Pusch - Massage Therapist

+
+
+
+

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.

+

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.

+

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.

+

I believe that every person and every body is different, which is why I always base my treatments on each client’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.

+

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.

+

I look forward to getting to know Studio Beve’s clients and using my professional experience to provide attentive, individually tailored treatments.

+
+
+ + + + +
+
+
+
+
+ + +
+
+
+
+ +

Treatments

+
+
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ +

Prices

+

The stated treatment time is hands-on treatment time. The appointment reserved for you is slightly longer, allowing time to change and to pay.

+
+ + service_category != $prevousCategory){ + if($prevousCategory !== ''){ + echo '
'; + } + ?> +
+

service_category;?>

+

service_description;?>

+
+ +
+
+
+
+
+
+
kr service_price;?>
+
+
+
+ service_category; + } + echo '
'; + } + ?> +
+
+
+ + +
+
+
+
+ +

Opening Hours

+

Free customer parking during your treatment

+
+
+
+
+
+
Weekdays
+
+
+
10:00 - 18:00
+
+
+
+
+
Saturday
+
+
+
10:00 - 16:00
+
+
+
+
+
Sunday
+
+
+
Closed
+
+
+
+
+
diff --git a/application/views/pages/includes/massage-form-hu.php b/application/views/pages/includes/massage-form-hu.php new file mode 100644 index 0000000..3c537c2 --- /dev/null +++ b/application/views/pages/includes/massage-form-hu.php @@ -0,0 +1,291 @@ + + + +
+ +
+ + +
+
+
+
+ +

Rólunk

+
+ + .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. */ ?> +
+
+

+ 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. +

+

+ 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. +

+

+ 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. +

+

+ 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. +

+

+ 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. +

+
+
+ +

Bence Evelin
Tulajdonos, Studio Beve

+
+
+ +
+
+ +

VINCENT Pusch - Masszőr

+
+
+
+

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.

+

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.

+

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.

+

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.

+

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.

+

Ö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.

+
+
+ + + + +
+
+
+
+
+ + +
+
+
+
+ +

Szolgáltatások

+
+
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ +

Árlista

+

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­öltözésre és a fizetésre.

+
+ + service_category != $prevousCategory){ + if($prevousCategory !== ''){ + echo '
'; + } + ?> +
+

service_category;?>

+

service_description;?>

+
+ +
+
+
+
+
+
+
kr service_price;?>
+
+
+
+ service_category; + } + echo '
'; + } + ?> +
+
+
+ + +
+
+
+
+ +

Nyitvatartás

+

Ingyenes vendégparkolás a kezelés ideje alatt

+
+
+
+
+
+
Hétköznap
+
+
+
10:00 - 18:00
+
+
+
+
+
Szombat
+
+
+
10:00 - 16:00
+
+
+
+
+
Vasárnap
+
+
+
Zárva
+
+
+
+
+
diff --git a/application/views/pages/includes/massage-form-no.php b/application/views/pages/includes/massage-form-no.php new file mode 100644 index 0000000..a784ef5 --- /dev/null +++ b/application/views/pages/includes/massage-form-no.php @@ -0,0 +1,293 @@ + + + +
+ +
+ + +
+
+
+
+ +

Om oss

+
+ + .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. */ ?> +
+
+

+ 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. +

+

+ 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. +

+

+ 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. +

+

+ 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. +

+

+ 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. +

+
+
+ +

Evelin Bence
Eieren, Studio Beve

+
+
+ +
+
+ +

VINCENT Pusch - Massør

+
+
+
+

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.

+

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.

+

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.

+

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.

+

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.

+

Jeg gleder meg til å bli kjent med Studio Beves kunder og til å bruke min faglige erfaring til å tilby individuelt tilpassede og omsorgsfulle behandlinger.

+
+
+ + + + +
+
+
+
+
+ + +
+
+
+
+ +

Behandlinger

+
+
+
+
+ + + +
+
+
+
+ + +
+
+
+
+ +

Priser

+

Oppgitt behandlingstid er ren behandlingstid. Timen som reserveres er noe lengre, slik at det er tid til av- og påkledning og betaling.

+
+ + service_category != $prevousCategory){ + if($prevousCategory !== ''){ + echo '
'; + } + ?> +
+

service_category;?>

+

service_description;?>

+
+ +
+
+
+
+
+
+
kr service_price;?>
+
+
+
+ service_category; + } + echo '
'; + } + ?> +
+
+
+ + +
+
+
+
+ +

Åpningstider

+

Gratis kunderparking under behandlingen

+
+
+
+
+
+
Ukedager
+
+
+
10:00 - 18:00
+
+
+
+
+
Lørdag
+
+
+
10:00 - 16:00
+
+
+
+
+
Søndag
+
+
+
Stengt
+
+
+
+
+
diff --git a/application/views/pages/manage-booking-cancelled.php b/application/views/pages/manage-booking-cancelled.php index 279f658..7490015 100755 --- a/application/views/pages/manage-booking-cancelled.php +++ b/application/views/pages/manage-booking-cancelled.php @@ -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 '.$booking->booking_date.' kl. '.date('H:i', strtotime($booking->booking_start_time)).' med '.htmlspecialchars($worker->worker_name, ENT_QUOTES, 'UTF-8').' er avbestilt.', @@ -36,9 +36,5 @@
diff --git a/application/views/pages/manage-booking.php b/application/views/pages/manage-booking.php index 470f214..d0b1dd9 100755 --- a/application/views/pages/manage-booking.php +++ b/application/views/pages/manage-booking.php @@ -1,9 +1,11 @@ serv_cat_slug !== $categorySlug); ?>
-
- service_name); ?> +
+
@@ -462,6 +464,19 @@ }); $('.service').click(function(){ + + /* 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'); + } + 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 += '
'+nameEl+'
'+priceEl+' kr
'; var currentTotal = $('#servicelength').val(); @@ -632,9 +649,5 @@ diff --git a/application/views/pages/vertical-booking-error.php b/application/views/pages/vertical-booking-error.php new file mode 100644 index 0000000..3a2e243 --- /dev/null +++ b/application/views/pages/vertical-booking-error.php @@ -0,0 +1,11 @@ + diff --git a/application/views/pages/vertical-booking-finished.php b/application/views/pages/vertical-booking-finished.php new file mode 100644 index 0000000..97c22c6 --- /dev/null +++ b/application/views/pages/vertical-booking-finished.php @@ -0,0 +1,11 @@ + diff --git a/application/views/pages/vertical-booking.php b/application/views/pages/vertical-booking.php new file mode 100644 index 0000000..6221b06 --- /dev/null +++ b/application/views/pages/vertical-booking.php @@ -0,0 +1,12 @@ + diff --git a/application/views/pages/vertical-landing.php b/application/views/pages/vertical-landing.php new file mode 100644 index 0000000..3c18e16 --- /dev/null +++ b/application/views/pages/vertical-landing.php @@ -0,0 +1,22 @@ + diff --git a/assets/css/main.css b/assets/css/main.css index b25cfe5..387cc6c 100755 --- a/assets/css/main.css +++ b/assets/css/main.css @@ -5136,6 +5136,8 @@ input, select, textarea { .tiles { + justify-content: center; + display: -moz-flex; display: -webkit-flex; @@ -5176,7 +5178,7 @@ input, select, textarea { position: relative; - width: 40%; + width: 28%; /* width: calc(33.33333% - 2.5em); */ @@ -5190,7 +5192,9 @@ input, select, textarea { .tiles article:hover { - background: #9e8462; + /* per-vertical hover fill lives on .style1/.style2/.style3 below; + + this used to hardcode barber gold for any tile without a style class */ } @@ -5204,9 +5208,9 @@ input, select, textarea { border-radius: 50px; - margin-right: 0; + margin-right: 1.5em; - margin-left: 5em; + margin-left: 1.5em; -moz-transition: background 0.2s ease-in-out; @@ -5226,9 +5230,9 @@ input, select, textarea { background: #000; - margin-left: 0; + margin-left: 1.5em; - margin-right: 5em; + margin-right: 1.5em; border-radius: 50px; @@ -5246,6 +5250,32 @@ input, select, textarea { + /* massage vertical */ + + .tiles article.style3{ + + background: #000; + + margin-left: 1.5em; + + margin-right: 1.5em; + + border-radius: 50px; + + border: 10px solid #9bbfae; + + -moz-transition: background 0.2s ease-in-out; + + -webkit-transition: background 0.2s ease-in-out; + + -ms-transition: background 0.2s ease-in-out; + + transition: background 0.2s ease-in-out; + + } + + + .tiles article.style1:hover{ background: #E4AD9C; @@ -5262,6 +5292,14 @@ input, select, textarea { + .tiles article.style3:hover{ + + background: #9bbfae; + + } + + + .tiles article > .image { -moz-transition: -moz-transform 0.5s ease; @@ -5464,10 +5502,24 @@ input, select, textarea { .tiles article > a h2 { - font-size: 52px; + /* Was 52px, chosen when there were two 40%-wide tiles with + + six-letter labels. Tiles are now 28% wide and MASSAGE is + + eight letters, which pushed the word out past the rounded + + border. 42px plus side padding keeps the longest label + + clear of the corner radius on all three tiles. */ + + font-size: 42px; font-family: 'Abril Fatface', cursive; + padding: 0 0.35em; + + text-align: center; + margin: 0; } @@ -5530,6 +5582,18 @@ input, select, textarea { + .tiles article.style3 > .image:before { + + background-size: cover; + + background-image: url(../../assets/img/massage.jpg); + + background-color: #5f7d6e; + + } + + + body:not(.is-touch) .tiles article:hover > .image { -moz-transform: scale(1.1); @@ -6814,21 +6878,25 @@ input, select, textarea { - .tiles article.style1 { + /* .style1 used to be position:absolute; right:0 here - a two-tile-only - position: absolute; + centring trick that overlapped the moment a third tile existed. - right: 0; + Normal flex flow handles any tile count. */ - margin-left: 0; + .tiles article.style1, - + .tiles article.style2, - } + .tiles article.style3 { - .tiles article.style2 { + position: relative; - margin-right: 0; + width: 28%; + + margin-left: 1em; + + margin-right: 1em; } @@ -6888,7 +6956,7 @@ input, select, textarea { - .tiles article.style1, .tiles article.style2{ + .tiles article.style1, .tiles article.style2, .tiles article.style3{ border-radius: 35px; @@ -6896,6 +6964,8 @@ input, select, textarea { margin-right: 2em; + width: 100%; + } @@ -6918,6 +6988,14 @@ input, select, textarea { + .tiles article.style3{ + + border: 7px solid #9bbfae; + + } + + + } diff --git a/assets/css/massage.css b/assets/css/massage.css new file mode 100644 index 0000000..3526303 --- /dev/null +++ b/assets/css/massage.css @@ -0,0 +1,4711 @@ +@import url(../css/fontawesome-all.min.css); +@import url("https://fonts.googleapis.com/css?family=Open+Sans:300,600,300italic,600italic"); + +/* + Catalyst by Pixelarity + pixelarity.com | hello@pixelarity.com + License: pixelarity.com/license +*/ + + +html{ + scroll-behavior: smooth; +} + +html, body, div, span, applet, object, +iframe, h1, h2, h3, h4, h5, h6, p, blockquote, +pre, a, abbr, acronym, address, big, cite, +code, del, dfn, em, img, ins, kbd, q, s, samp, +small, strike, strong, sub, sup, tt, var, b, +u, i, center, dl, dt, dd, ol, ul, li, fieldset, +form, label, legend, table, caption, tbody, +tfoot, thead, tr, th, td, article, aside, +canvas, details, embed, figure, figcaption, +footer, header, hgroup, menu, nav, output, ruby, +section, summary, time, mark, audio, video { + margin: 0; + padding: 0; + border: 0; + font-size: 100%; + font: inherit; + vertical-align: baseline;} + +article, aside, details, figcaption, figure, +footer, header, hgroup, menu, nav, section { + display: block;} + +body { + line-height: 1; + font-family: 'Poppins', sans-serif; +} + +ol, ul { + list-style: none; +} + +blockquote, q { + quotes: none; +} + + blockquote:before, blockquote:after, q:before, q:after { + content: ''; + content: none; + } + +table { + border-collapse: collapse; + border-spacing: 0; +} + +body { + -webkit-text-size-adjust: none; +} + +mark { + background-color: transparent; + color: inherit; +} + +input::-moz-focus-inner { + border: 0; + padding: 0; +} + +input, select, textarea { + -moz-appearance: none; + -webkit-appearance: none; + -ms-appearance: none; + appearance: none; +} + +.profileRow{ + background-color: #cfe0d6; + border: 1px solid #5f7d6e; +} + +.profileRadioBtn{ + /*background-color: #5f7d6e;*/ +} + +#totalPanelBtn{ + background-color: #5f7d6e; +} + +#totalPanel{ + border: 1px solid #5f7d6e; + background-color: #f5faf7; +} + +.serviceInfoIcon .fa-info-circle{ + color:#5f7d6e; +} + +.serviceCheckCol input[type="checkbox"]{ + border:1px solid #5f7d6e; +} + +.bookingButton{ + background-color: #5f7d6e!important; +} + +.availableBookingTime{ + background-color: #cfe0d6; +} + +.activeStep{ + background-color:#5f7d6e; +} + +.bookingName, .bookingEmail, .bookingPhone{ + background-color: #f5faf7; + border:1px solid #5f7d6e!important; +} + +.bookingEmailTitle, .bookingPhoneTitle{ + color:gray; +} + +.bookingTitle{ + color:#5f7d6e; +} + +.stepTitle{ + color:#5f7d6e; +} + +.stepLine { + border-top: 1px solid #9bbfae; +} + +.stepCircle { + border: 1px solid #5f7d6e; + background-color: #cfe0d6; +} + +.panel-heading{ + color:#5f7d6e!important; + text-transform: uppercase!important; +} + +.serviceCol h4{ + color:#5f7d6e!important; +} + +.serviceHeader .serviceNameCol, +.serviceHeader .servicePriceCol{ + color:#9bbfae!important; + font-weight:normal!important; +} + +.BookingStepContainer { + border: 1px solid #5f7d6e; +} + +.bookingSubmit{ + background-color: #5f7d6e!important; +} + +/* Basic */ + + html { + box-sizing: border-box; + } + + *, *:before, *:after { + box-sizing: inherit; + } + + body { + background: #fff; + } + + body.is-preload *, body.is-preload *:before, body.is-preload *:after { + -moz-animation: none !important; + -webkit-animation: none !important; + -ms-animation: none !important; + animation: none !important; + -moz-transition: none !important; + -webkit-transition: none !important; + -ms-transition: none !important; + transition: none !important; + } + + body, input, select, textarea { + color: #4a4a4a; + background: #fff; + font-family: "Poppins", sans-serif; + font-size: 13pt; + font-weight: 300; + line-height: 1.75em; + } + + a { + color: #51c5e8; + text-decoration: underline; + } + + a:hover { + text-decoration: none; + } + + strong, b { + color: #2a2a2a; + font-weight: 600; + } + + em, i { + font-style: italic; + } + + p { + margin: 0 0 2em 0; + } + + h1, h2, h3, h4, h5, h6 { + color: #2a2a2a; + font-weight: 600; + letter-spacing: 0.125em; + line-height: 1em; + margin: 0 0 1em 0; + } + + h1 a, h2 a, h3 a, h4 a, h5 a, h6 a { + color: inherit; + text-decoration: none; + } + + h2 { + font-size: 1.5em; + line-height: 1.75em; + } + + h3 { + font-size: 1.25em; + line-height: 1.5em; + /*color: #5f7d6e;*/ + color: #9bbfae; + } + + h4 { + font-size: 1.1em; + line-height: 1.5em; + } + + h5 { + font-size: 0.9em; + line-height: 1.5em; + } + + h6 { + font-size: 0.7em; + line-height: 1.5em; + } + + sub { + font-size: 0.8em; + position: relative; + top: 0.5em; + } + + sup { + font-size: 0.8em; + position: relative; + top: -0.5em; + } + + hr { + border: 0; + border-bottom: solid 3px #eee; + margin: 3em 0; + } + + hr.major { + margin: 5em 0; + } + + blockquote { + border-left: solid 8px #eee; + font-style: italic; + margin: 0 0 2em 0; + padding: 0.5em 0 0.5em 2em; + } + + pre { + -webkit-overflow-scrolling: touch; + background: rgba(144, 144, 144, 0.05); + font-family: "Courier New", monospace; + font-size: 0.9em; + line-height: 1.5em; + margin: 0 0 2em 0; + overflow-x: auto; + padding: 1em 1.5em; + } + + code { + background: rgba(144, 144, 144, 0.05); + font-family: "Courier New", monospace; + font-size: 0.9em; + margin: 0 0.25em; + padding: 0.25em 0.65em; + } + + .align-left { + text-align: left; + } + + .align-center { + text-align: center; + } + + .align-right { + text-align: right; + } + +/* Row */ + + .row { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + align-items: stretch; + } + + .row > * { + box-sizing: border-box; + } + + .row.gtr-uniform > * > :last-child { + margin-bottom: 0; + } + + .row.aln-left { + justify-content: flex-start; + } + + .row.aln-center { + justify-content: center; + } + + .row.aln-right { + justify-content: flex-end; + } + + .row.aln-top { + align-items: flex-start; + } + + .row.aln-middle { + align-items: center; + } + + .row.aln-bottom { + align-items: flex-end; + } + + .row > .imp { + order: -1; + } + + .row > .col-1 { + width: 8.33333%; + } + + .row > .off-1 { + margin-left: 8.33333%; + } + + .row > .col-2 { + width: 16.66667%; + } + + .row > .off-2 { + margin-left: 16.66667%; + } + + .row > .col-3 { + width: 25%; + } + + .row > .off-3 { + margin-left: 25%; + } + + .row > .col-4 { + width: 33.33333%; + } + + .row > .off-4 { + margin-left: 33.33333%; + } + + .row > .col-5 { + width: 41.66667%; + } + + .row > .off-5 { + margin-left: 41.66667%; + } + + .row > .col-6 { + width: 50%; + } + + .row > .off-6 { + margin-left: 50%; + } + + .row > .col-7 { + width: 58.33333%; + } + + .row > .off-7 { + margin-left: 58.33333%; + } + + .row > .col-8 { + width: 66.66667%; + } + + .row > .off-8 { + margin-left: 66.66667%; + } + + .row > .col-9 { + width: 75%; + } + + .row > .off-9 { + margin-left: 75%; + } + + .row > .col-10 { + width: 83.33333%; + } + + .row > .off-10 { + margin-left: 83.33333%; + } + + .row > .col-11 { + width: 91.66667%; + } + + .row > .off-11 { + margin-left: 91.66667%; + } + + .row > .col-12 { + width: 100%; + } + + .row > .off-12 { + margin-left: 100%; + } + + .row.gtr-0 { + margin-top: 0; + margin-left: 0em; + } + + .row.gtr-0 > * { + padding: 0 0 0 0em; + } + + .row.gtr-0.gtr-uniform { + margin-top: 0em; + } + + .row.gtr-0.gtr-uniform > * { + padding-top: 0em; + } + + .row.gtr-25 { + margin-top: 0; + margin-left: -0.75em; + } + + .row.gtr-25 > * { + padding: 0 0 0 0.75em; + } + + .row.gtr-25.gtr-uniform { + margin-top: -0.75em; + } + + .row.gtr-25.gtr-uniform > * { + padding-top: 0.75em; + } + + .row.gtr-50 { + margin-top: 0; + margin-left: -1.5em; + } + + .row.gtr-50 > * { + padding: 0 0 0 1.5em; + } + + .row.gtr-50.gtr-uniform { + margin-top: -1.5em; + } + + .row.gtr-50.gtr-uniform > * { + padding-top: 1.5em; + } + + .row { + margin-top: 0; + margin-left: -3em; + } + + .row > * { + padding: 0 0 0 3em; + } + + .row.gtr-uniform { + margin-top: -3em; + } + + .row.gtr-uniform > * { + padding-top: 3em; + } + + .row.gtr-150 { + margin-top: 0; + margin-left: -4.5em; + } + + .row.gtr-150 > * { + padding: 0 0 0 4.5em; + } + + .row.gtr-150.gtr-uniform { + margin-top: -4.5em; + } + + .row.gtr-150.gtr-uniform > * { + padding-top: 4.5em; + } + + .row.gtr-200 { + margin-top: 0; + margin-left: -6em; + } + + .row.gtr-200 > * { + padding: 0 0 0 6em; + } + + .row.gtr-200.gtr-uniform { + margin-top: -6em; + } + + .row.gtr-200.gtr-uniform > * { + padding-top: 6em; + } + + @media screen and (max-width: 1680px) { + + .row { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + align-items: stretch; + } + + .row > * { + box-sizing: border-box; + } + + .row.gtr-uniform > * > :last-child { + margin-bottom: 0; + } + + .row.aln-left { + justify-content: flex-start; + } + + .row.aln-center { + justify-content: center; + } + + .row.aln-right { + justify-content: flex-end; + } + + .row.aln-top { + align-items: flex-start; + } + + .row.aln-middle { + align-items: center; + } + + .row.aln-bottom { + align-items: flex-end; + } + + .row > .imp-wide { + order: -1; + } + + .row > .col-1-wide { + width: 8.33333%; + } + + .row > .off-1-wide { + margin-left: 8.33333%; + } + + .row > .col-2-wide { + width: 16.66667%; + } + + .row > .off-2-wide { + margin-left: 16.66667%; + } + + .row > .col-3-wide { + width: 25%; + } + + .row > .off-3-wide { + margin-left: 25%; + } + + .row > .col-4-wide { + width: 33.33333%; + } + + .row > .off-4-wide { + margin-left: 33.33333%; + } + + .row > .col-5-wide { + width: 41.66667%; + } + + .row > .off-5-wide { + margin-left: 41.66667%; + } + + .row > .col-6-wide { + width: 50%; + } + + .row > .off-6-wide { + margin-left: 50%; + } + + .row > .col-7-wide { + width: 58.33333%; + } + + .row > .off-7-wide { + margin-left: 58.33333%; + } + + .row > .col-8-wide { + width: 66.66667%; + } + + .row > .off-8-wide { + margin-left: 66.66667%; + } + + .row > .col-9-wide { + width: 75%; + } + + .row > .off-9-wide { + margin-left: 75%; + } + + .row > .col-10-wide { + width: 83.33333%; + } + + .row > .off-10-wide { + margin-left: 83.33333%; + } + + .row > .col-11-wide { + width: 91.66667%; + } + + .row > .off-11-wide { + margin-left: 91.66667%; + } + + .row > .col-12-wide { + width: 100%; + } + + .row > .off-12-wide { + margin-left: 100%; + } + + .row.gtr-0 { + margin-top: 0; + margin-left: 0em; + } + + .row.gtr-0 > * { + padding: 0 0 0 0em; + } + + .row.gtr-0.gtr-uniform { + margin-top: 0em; + } + + .row.gtr-0.gtr-uniform > * { + padding-top: 0em; + } + + .row.gtr-25 { + margin-top: 0; + margin-left: -0.75em; + } + + .row.gtr-25 > * { + padding: 0 0 0 0.75em; + } + + .row.gtr-25.gtr-uniform { + margin-top: -0.75em; + } + + .row.gtr-25.gtr-uniform > * { + padding-top: 0.75em; + } + + .row.gtr-50 { + margin-top: 0; + margin-left: -1.5em; + } + + .row.gtr-50 > * { + padding: 0 0 0 1.5em; + } + + .row.gtr-50.gtr-uniform { + margin-top: -1.5em; + } + + .row.gtr-50.gtr-uniform > * { + padding-top: 1.5em; + } + + .row { + margin-top: 0; + margin-left: -3em; + } + + .row > * { + padding: 0 0 0 3em; + } + + .row.gtr-uniform { + margin-top: -3em; + } + + .row.gtr-uniform > * { + padding-top: 3em; + } + + .row.gtr-150 { + margin-top: 0; + margin-left: -4.5em; + } + + .row.gtr-150 > * { + padding: 0 0 0 4.5em; + } + + .row.gtr-150.gtr-uniform { + margin-top: -4.5em; + } + + .row.gtr-150.gtr-uniform > * { + padding-top: 4.5em; + } + + .row.gtr-200 { + margin-top: 0; + margin-left: -6em; + } + + .row.gtr-200 > * { + padding: 0 0 0 6em; + } + + .row.gtr-200.gtr-uniform { + margin-top: -6em; + } + + .row.gtr-200.gtr-uniform > * { + padding-top: 6em; + } + + } + + @media screen and (max-width: 1280px) { + + .row { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + align-items: stretch; + } + + .row > * { + box-sizing: border-box; + } + + .row.gtr-uniform > * > :last-child { + margin-bottom: 0; + } + + .row.aln-left { + justify-content: flex-start; + } + + .row.aln-center { + justify-content: center; + } + + .row.aln-right { + justify-content: flex-end; + } + + .row.aln-top { + align-items: flex-start; + } + + .row.aln-middle { + align-items: center; + } + + .row.aln-bottom { + align-items: flex-end; + } + + .row > .imp-normal { + order: -1; + } + + .row > .col-1-normal { + width: 8.33333%; + } + + .row > .off-1-normal { + margin-left: 8.33333%; + } + + .row > .col-2-normal { + width: 16.66667%; + } + + .row > .off-2-normal { + margin-left: 16.66667%; + } + + .row > .col-3-normal { + width: 25%; + } + + .row > .off-3-normal { + margin-left: 25%; + } + + .row > .col-4-normal { + width: 33.33333%; + } + + .row > .off-4-normal { + margin-left: 33.33333%; + } + + .row > .col-5-normal { + width: 41.66667%; + } + + .row > .off-5-normal { + margin-left: 41.66667%; + } + + .row > .col-6-normal { + width: 50%; + } + + .row > .off-6-normal { + margin-left: 50%; + } + + .row > .col-7-normal { + width: 58.33333%; + } + + .row > .off-7-normal { + margin-left: 58.33333%; + } + + .row > .col-8-normal { + width: 66.66667%; + } + + .row > .off-8-normal { + margin-left: 66.66667%; + } + + .row > .col-9-normal { + width: 75%; + } + + .row > .off-9-normal { + margin-left: 75%; + } + + .row > .col-10-normal { + width: 83.33333%; + } + + .row > .off-10-normal { + margin-left: 83.33333%; + } + + .row > .col-11-normal { + width: 91.66667%; + } + + .row > .off-11-normal { + margin-left: 91.66667%; + } + + .row > .col-12-normal { + width: 100%; + } + + .row > .off-12-normal { + margin-left: 100%; + } + + .row.gtr-0 { + margin-top: 0; + margin-left: 0em; + } + + .row.gtr-0 > * { + padding: 0 0 0 0em; + } + + .row.gtr-0.gtr-uniform { + margin-top: 0em; + } + + .row.gtr-0.gtr-uniform > * { + padding-top: 0em; + } + + .row.gtr-25 { + margin-top: 0; + margin-left: -0.5em; + } + + .row.gtr-25 > * { + padding: 0 0 0 0.5em; + } + + .row.gtr-25.gtr-uniform { + margin-top: -0.5em; + } + + .row.gtr-25.gtr-uniform > * { + padding-top: 0.5em; + } + + .row.gtr-50 { + margin-top: 0; + margin-left: -1em; + } + + .row.gtr-50 > * { + padding: 0 0 0 1em; + } + + .row.gtr-50.gtr-uniform { + margin-top: -1em; + } + + .row.gtr-50.gtr-uniform > * { + padding-top: 1em; + } + + .row { + margin-top: 0; + margin-left: -2em; + } + + .row > * { + padding: 0 0 0 2em; + } + + .row.gtr-uniform { + margin-top: -2em; + } + + .row.gtr-uniform > * { + padding-top: 2em; + } + + .row.gtr-150 { + margin-top: 0; + margin-left: -3em; + } + + .row.gtr-150 > * { + padding: 0 0 0 3em; + } + + .row.gtr-150.gtr-uniform { + margin-top: -3em; + } + + .row.gtr-150.gtr-uniform > * { + padding-top: 3em; + } + + .row.gtr-200 { + margin-top: 0; + margin-left: -4em; + } + + .row.gtr-200 > * { + padding: 0 0 0 4em; + } + + .row.gtr-200.gtr-uniform { + margin-top: -4em; + } + + .row.gtr-200.gtr-uniform > * { + padding-top: 4em; + } + + } + + @media screen and (max-width: 980px) { + + .row { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + align-items: stretch; + } + + .row > * { + box-sizing: border-box; + } + + .row.gtr-uniform > * > :last-child { + margin-bottom: 0; + } + + .row.aln-left { + justify-content: flex-start; + } + + .row.aln-center { + justify-content: center; + } + + .row.aln-right { + justify-content: flex-end; + } + + .row.aln-top { + align-items: flex-start; + } + + .row.aln-middle { + align-items: center; + } + + .row.aln-bottom { + align-items: flex-end; + } + + .row > .imp-narrow { + order: -1; + } + + .row > .col-1-narrow { + width: 8.33333%; + } + + .row > .off-1-narrow { + margin-left: 8.33333%; + } + + .row > .col-2-narrow { + width: 16.66667%; + } + + .row > .off-2-narrow { + margin-left: 16.66667%; + } + + .row > .col-3-narrow { + width: 25%; + } + + .row > .off-3-narrow { + margin-left: 25%; + } + + .row > .col-4-narrow { + width: 33.33333%; + } + + .row > .off-4-narrow { + margin-left: 33.33333%; + } + + .row > .col-5-narrow { + width: 41.66667%; + } + + .row > .off-5-narrow { + margin-left: 41.66667%; + } + + .row > .col-6-narrow { + width: 50%; + } + + .row > .off-6-narrow { + margin-left: 50%; + } + + .row > .col-7-narrow { + width: 58.33333%; + } + + .row > .off-7-narrow { + margin-left: 58.33333%; + } + + .row > .col-8-narrow { + width: 66.66667%; + } + + .row > .off-8-narrow { + margin-left: 66.66667%; + } + + .row > .col-9-narrow { + width: 75%; + } + + .row > .off-9-narrow { + margin-left: 75%; + } + + .row > .col-10-narrow { + width: 83.33333%; + } + + .row > .off-10-narrow { + margin-left: 83.33333%; + } + + .row > .col-11-narrow { + width: 91.66667%; + } + + .row > .off-11-narrow { + margin-left: 91.66667%; + } + + .row > .col-12-narrow { + width: 100%; + } + + .row > .off-12-narrow { + margin-left: 100%; + } + + .row.gtr-0 { + margin-top: 0; + margin-left: 0em; + } + + .row.gtr-0 > * { + padding: 0 0 0 0em; + } + + .row.gtr-0.gtr-uniform { + margin-top: 0em; + } + + .row.gtr-0.gtr-uniform > * { + padding-top: 0em; + } + + .row.gtr-25 { + margin-top: 0; + margin-left: -0.5em; + } + + .row.gtr-25 > * { + padding: 0 0 0 0.5em; + } + + .row.gtr-25.gtr-uniform { + margin-top: -0.5em; + } + + .row.gtr-25.gtr-uniform > * { + padding-top: 0.5em; + } + + .row.gtr-50 { + margin-top: 0; + margin-left: -1em; + } + + .row.gtr-50 > * { + padding: 0 0 0 1em; + } + + .row.gtr-50.gtr-uniform { + margin-top: -1em; + } + + .row.gtr-50.gtr-uniform > * { + padding-top: 1em; + } + + .row { + margin-top: 0; + margin-left: -2em; + } + + .row > * { + padding: 0 0 0 2em; + } + + .row.gtr-uniform { + margin-top: -2em; + } + + .row.gtr-uniform > * { + padding-top: 2em; + } + + .row.gtr-150 { + margin-top: 0; + margin-left: -3em; + } + + .row.gtr-150 > * { + padding: 0 0 0 3em; + } + + .row.gtr-150.gtr-uniform { + margin-top: -3em; + } + + .row.gtr-150.gtr-uniform > * { + padding-top: 3em; + } + + .row.gtr-200 { + margin-top: 0; + margin-left: -4em; + } + + .row.gtr-200 > * { + padding: 0 0 0 4em; + } + + .row.gtr-200.gtr-uniform { + margin-top: -4em; + } + + .row.gtr-200.gtr-uniform > * { + padding-top: 4em; + } + + } + + @media screen and (max-width: 736px) { + + .row { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + align-items: stretch; + } + + .row > * { + box-sizing: border-box; + } + + .row.gtr-uniform > * > :last-child { + margin-bottom: 0; + } + + .row.aln-left { + justify-content: flex-start; + } + + .row.aln-center { + justify-content: center; + } + + .row.aln-right { + justify-content: flex-end; + } + + .row.aln-top { + align-items: flex-start; + } + + .row.aln-middle { + align-items: center; + } + + .row.aln-bottom { + align-items: flex-end; + } + + .row > .imp-mobile { + order: -1; + } + + .row > .col-1-mobile { + width: 8.33333%; + } + + .row > .off-1-mobile { + margin-left: 8.33333%; + } + + .row > .col-2-mobile { + width: 16.66667%; + } + + .row > .off-2-mobile { + margin-left: 16.66667%; + } + + .row > .col-3-mobile { + width: 25%; + } + + .row > .off-3-mobile { + margin-left: 25%; + } + + .row > .col-4-mobile { + width: 33.33333%; + } + + .row > .off-4-mobile { + margin-left: 33.33333%; + } + + .row > .col-5-mobile { + width: 41.66667%; + } + + .row > .off-5-mobile { + margin-left: 41.66667%; + } + + .row > .col-6-mobile { + width: 50%; + } + + .row > .off-6-mobile { + margin-left: 50%; + } + + .row > .col-7-mobile { + width: 58.33333%; + } + + .row > .off-7-mobile { + margin-left: 58.33333%; + } + + .row > .col-8-mobile { + width: 66.66667%; + } + + .row > .off-8-mobile { + margin-left: 66.66667%; + } + + .row > .col-9-mobile { + width: 75%; + } + + .row > .off-9-mobile { + margin-left: 75%; + } + + .row > .col-10-mobile { + width: 83.33333%; + } + + .row > .off-10-mobile { + margin-left: 83.33333%; + } + + .row > .col-11-mobile { + width: 91.66667%; + } + + .row > .off-11-mobile { + margin-left: 91.66667%; + } + + .row > .col-12-mobile { + width: 100%; + } + + .row > .off-12-mobile { + margin-left: 100%; + } + + .row.gtr-0 { + margin-top: 0; + margin-left: 0em; + } + + .row.gtr-0 > * { + padding: 0 0 0 0em; + } + + .row.gtr-0.gtr-uniform { + margin-top: 0em; + } + + .row.gtr-0.gtr-uniform > * { + padding-top: 0em; + } + + .row.gtr-25 { + margin-top: 0; + margin-left: -0.5em; + } + + .row.gtr-25 > * { + padding: 0 0 0 0.5em; + } + + .row.gtr-25.gtr-uniform { + margin-top: -0.5em; + } + + .row.gtr-25.gtr-uniform > * { + padding-top: 0.5em; + } + + .row.gtr-50 { + margin-top: 0; + margin-left: -1em; + } + + .row.gtr-50 > * { + padding: 0 0 0 1em; + } + + .row.gtr-50.gtr-uniform { + margin-top: -1em; + } + + .row.gtr-50.gtr-uniform > * { + padding-top: 1em; + } + + .row { + margin-top: 0; + margin-left: -2em; + } + + .row > * { + padding: 0 0 0 2em; + } + + .row.gtr-uniform { + margin-top: -2em; + } + + .row.gtr-uniform > * { + padding-top: 2em; + } + + .row.gtr-150 { + margin-top: 0; + margin-left: -3em; + } + + .row.gtr-150 > * { + padding: 0 0 0 3em; + } + + .row.gtr-150.gtr-uniform { + margin-top: -3em; + } + + .row.gtr-150.gtr-uniform > * { + padding-top: 3em; + } + + .row.gtr-200 { + margin-top: 0; + margin-left: -4em; + } + + .row.gtr-200 > * { + padding: 0 0 0 4em; + } + + .row.gtr-200.gtr-uniform { + margin-top: -4em; + } + + .row.gtr-200.gtr-uniform > * { + padding-top: 4em; + } + + } + + @media screen and (max-width: 480px) { + + .row { + display: flex; + flex-wrap: wrap; + box-sizing: border-box; + align-items: stretch; + } + + .row > * { + box-sizing: border-box; + } + + .row.gtr-uniform > * > :last-child { + margin-bottom: 0; + } + + .row.aln-left { + justify-content: flex-start; + } + + .row.aln-center { + justify-content: center; + } + + .row.aln-right { + justify-content: flex-end; + } + + .row.aln-top { + align-items: flex-start; + } + + .row.aln-middle { + align-items: center; + } + + .row.aln-bottom { + align-items: flex-end; + } + + .row > .imp-mobilep { + order: -1; + } + + .row > .col-1-mobilep { + width: 8.33333%; + } + + .row > .off-1-mobilep { + margin-left: 8.33333%; + } + + .row > .col-2-mobilep { + width: 16.66667%; + } + + .row > .off-2-mobilep { + margin-left: 16.66667%; + } + + .row > .col-3-mobilep { + width: 25%; + } + + .row > .off-3-mobilep { + margin-left: 25%; + } + + .row > .col-4-mobilep { + width: 33.33333%; + } + + .row > .off-4-mobilep { + margin-left: 33.33333%; + } + + .row > .col-5-mobilep { + width: 41.66667%; + } + + .row > .off-5-mobilep { + margin-left: 41.66667%; + } + + .row > .col-6-mobilep { + width: 50%; + } + + .row > .off-6-mobilep { + margin-left: 50%; + } + + .row > .col-7-mobilep { + width: 58.33333%; + } + + .row > .off-7-mobilep { + margin-left: 58.33333%; + } + + .row > .col-8-mobilep { + width: 66.66667%; + } + + .row > .off-8-mobilep { + margin-left: 66.66667%; + } + + .row > .col-9-mobilep { + width: 75%; + } + + .row > .off-9-mobilep { + margin-left: 75%; + } + + .row > .col-10-mobilep { + width: 83.33333%; + } + + .row > .off-10-mobilep { + margin-left: 83.33333%; + } + + .row > .col-11-mobilep { + width: 91.66667%; + } + + .row > .off-11-mobilep { + margin-left: 91.66667%; + } + + .row > .col-12-mobilep { + width: 100%; + } + + .row > .off-12-mobilep { + margin-left: 100%; + } + + .row.gtr-0 { + margin-top: 0; + margin-left: 0em; + } + + .row.gtr-0 > * { + padding: 0 0 0 0em; + } + + .row.gtr-0.gtr-uniform { + margin-top: 0em; + } + + .row.gtr-0.gtr-uniform > * { + padding-top: 0em; + } + + .row.gtr-25 { + margin-top: 0; + margin-left: -0.5em; + } + + .row.gtr-25 > * { + padding: 0 0 0 0.5em; + } + + .row.gtr-25.gtr-uniform { + margin-top: -0.5em; + } + + .row.gtr-25.gtr-uniform > * { + padding-top: 0.5em; + } + + .row.gtr-50 { + margin-top: 0; + margin-left: -1em; + } + + .row.gtr-50 > * { + padding: 0 0 0 1em; + } + + .row.gtr-50.gtr-uniform { + margin-top: -1em; + } + + .row.gtr-50.gtr-uniform > * { + padding-top: 1em; + } + + .row { + margin-top: 0; + margin-left: -2em; + } + + .row > * { + padding: 0 0 0 2em; + } + + .row.gtr-uniform { + margin-top: -2em; + } + + .row.gtr-uniform > * { + padding-top: 2em; + } + + .row.gtr-150 { + margin-top: 0; + margin-left: -3em; + } + + .row.gtr-150 > * { + padding: 0 0 0 3em; + } + + .row.gtr-150.gtr-uniform { + margin-top: -3em; + } + + .row.gtr-150.gtr-uniform > * { + padding-top: 3em; + } + + .row.gtr-200 { + margin-top: 0; + margin-left: -4em; + } + + .row.gtr-200 > * { + padding: 0 0 0 4em; + } + + .row.gtr-200.gtr-uniform { + margin-top: -4em; + } + + .row.gtr-200.gtr-uniform > * { + padding-top: 4em; + } + + } + +/* Container */ + + .container { + margin: 0 auto; + width: 1200px; + } + + .container.medium { + width: 900px; + } + + @media screen and (max-width: 1680px) { + + .container { + width: 1080px; + } + + .container.medium { + width: 810px; + } + + } + + @media screen and (max-width: 1280px) { + + .container { + width: 960px; + } + + .container.medium { + width: 720px; + } + + } + + @media screen and (max-width: 980px) { + + .container { + width: 90%; + } + + .container.medium { + width: 90%; + } + + } + + @media screen and (max-width: 736px) { + + .container { + width: 100%; + } + + .container.medium { + width: 100%; + } + + } + +/* Section/Article */ + + section.special, article.special { + text-align: center; + } + + header.major { + text-align: center; + position: relative; + } + + header.major:after { + background: #888; + content: ''; + display: inline-block; + height: 3px; + margin: 0 0 2em 0; + width: 6em; + } + + header.major.alt { + text-align: center; + margin-bottom: 2em; + } + + header p { + letter-spacing: 0.125em; + margin: 0 0 1.5em 0; + position: relative; + text-transform: uppercase; + } + + header h2 + p { + font-size: 1em; + margin-top: -0.85em; + line-height: 1.75em; + } + + header h3 + p { + font-size: 0.9em; + margin-top: -0.75em; + line-height: 1.75em; + } + + header h4 + p, + header h5 + p, + header h6 + p { + font-size: 0.8em; + margin-top: -0.75em; + line-height: 1.75em; + } + +/* Form */ + + form { + margin: 0 0 2em 0; + } + + label { + color: #2a2a2a; + display: block; + font-size: 0.9em; + font-weight: 600; + margin: 0 0 1em 0; + } + + input[type="text"], + input[type="password"], + input[type="email"], + select, + textarea { + -moz-appearance: none; + -webkit-appearance: none; + -ms-appearance: none; + appearance: none; + border: none; + border: solid 2px #eee; + border-radius: 0; + color: inherit; + display: block; + outline: 0; + padding: 0 1em; + text-decoration: none; + width: 100%; + } + + input[type="text"]:invalid, + input[type="password"]:invalid, + input[type="email"]:invalid, + select:invalid, + textarea:invalid { + box-shadow: none; + } + + input[type="text"]:focus, + input[type="password"]:focus, + input[type="email"]:focus, + select:focus, + textarea:focus { + border-color: #51c5e8; + } + + select { + background-image: url("data:image/svg+xml;charset=utf8,%3Csvg xmlns='http://www.w3.org/2000/svg' width='40' height='40' preserveAspectRatio='none' viewBox='0 0 40 40'%3E%3Cpath d='M9.4,12.3l10.4,10.4l10.4-10.4c0.2-0.2,0.5-0.4,0.9-0.4c0.3,0,0.6,0.1,0.9,0.4l3.3,3.3c0.2,0.2,0.4,0.5,0.4,0.9 c0,0.4-0.1,0.6-0.4,0.9L20.7,31.9c-0.2,0.2-0.5,0.4-0.9,0.4c-0.3,0-0.6-0.1-0.9-0.4L4.3,17.3c-0.2-0.2-0.4-0.5-0.4-0.9 c0-0.4,0.1-0.6,0.4-0.9l3.3-3.3c0.2-0.2,0.5-0.4,0.9-0.4S9.1,12.1,9.4,12.3z' fill='%23eee' /%3E%3C/svg%3E"); + background-size: 1.25em; + background-repeat: no-repeat; + background-position: calc(100% - 1em) center; + height: 3.5em; + padding-right: 3.5em; + text-overflow: ellipsis; + } + + select:focus::-ms-value { + background-color: transparent; + } + + select::-ms-expand { + display: none; + } + + select option { + color: #2a2a2a; + background: #fff; + } + + input[type="text"], + input[type="password"], + input[type="email"], + select { + height: 3.5em; + } + + textarea { + padding: 0.75em 1em; + } + + input[type="checkbox"], + input[type="radio"] { + -moz-appearance: none; + -webkit-appearance: none; + -ms-appearance: none; + appearance: none; + display: block; + float: left; + margin-right: -2em; + opacity: 0; + width: 1em; + z-index: -1; + } + + input[type="checkbox"] + label, + input[type="radio"] + label { + text-decoration: none; + color: #4a4a4a; + cursor: pointer; + display: inline-block; + font-size: 1em; + font-weight: 300; + padding-left: 2.85em; + padding-right: 0.75em; + position: relative; + } + + input[type="checkbox"] + label:before, + input[type="radio"] + label:before { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; + text-transform: none !important; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + } + + input[type="checkbox"] + label:before, + input[type="radio"] + label:before { + background: rgba(144, 144, 144, 0.05); + border: solid 2px #eee; + content: ''; + display: inline-block; + font-size: 0.8em; + height: 2.1em; + left: 0; + line-height: 1.925em; + position: absolute; + text-align: center; + top: 0; + width: 2.1em; + } + + input[type="checkbox"]:checked + label:before, + input[type="radio"]:checked + label:before { + background: #1d2726; + border-color: #1d2726; + color: #ffffff; + content: '\f00c'; + } + + input[type="checkbox"]:focus + label:before, + input[type="radio"]:focus + label:before { + border-color: #51c5e8; + } + + input[type="radio"] + label:before { + border-radius: 100%; + } + + ::-webkit-input-placeholder { + color: #bbb !important; + opacity: 1.0; + } + + :-moz-placeholder { + color: #bbb !important; + opacity: 1.0; + } + + ::-moz-placeholder { + color: #bbb !important; + opacity: 1.0; + } + + :-ms-input-placeholder { + color: #bbb !important; + opacity: 1.0; + } + +/* Box */ + + .box { + border: solid 2px #eee; + margin-bottom: 2em; + padding: 1.5em; + } + + .box > :last-child, + .box > :last-child > :last-child, + .box > :last-child > :last-child > :last-child { + margin-bottom: 0; + } + + .box.alt { + border: 0; + padding: 0; + } + +/* Icon */ + + .icon { + text-decoration: none; + border-bottom: none; + position: relative; + } + + .icon:before { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; + text-transform: none !important; + font-family: 'Font Awesome 5 Free'; + font-weight: 400; + } + + .icon.solid:before { + font-weight: 900; + } + + .icon.brands:before { + font-family: 'Font Awesome 5 Brands'; + } + + .icon > .label { + display: none; + } + +/* Image */ + + .image { + border: 0; + display: inline-block; + position: relative; + } + + .image:before { + background: url("images/overlay.png"); + content: ''; + display: block; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + } + + .image img { + display: block; + } + + .image.left { + float: left; + margin: 0 1.5em 1em 0; + top: 0.25em; + } + + .image.right { + float: right; + margin: 0 0 1em 1.5em; + top: 0.25em; + } + + .image.fit { + display: block; + margin: 0 0 3em 0; + width: 100%; + } + + .image.fit img { + display: block; + width: 100%; + } + +/* List */ + + ol { + list-style: decimal; + margin: 0 0 2em 0; + padding-left: 1.25em; + } + + ol li { + padding-left: 0.25em; + } + + ul { + list-style: disc; + margin: 0 0 2em 0; + padding-left: 1em; + } + + ul li { + padding-left: 0.5em; + } + + ul.alt { + list-style: none; + padding-left: 0; + } + + ul.alt li { + border-top: solid 1px #eee; + padding: 0.5em 0; + } + + ul.alt li:first-child { + border-top: 0; + padding-top: 0; + } + + dl { + margin: 0 0 2em 0; + } + +/* Actions */ + + ul.actions { + display: -moz-flex; + display: -webkit-flex; + display: -ms-flex; + display: flex; + cursor: default; + list-style: none; + margin-left: -1em; + padding-left: 0; + } + + ul.actions li { + padding: 0 0 0 1em; + vertical-align: middle; + } + + ul.actions.special { + -moz-justify-content: center; + -webkit-justify-content: center; + -ms-justify-content: center; + justify-content: center; + width: 100%; + margin-left: 0; + } + + ul.actions.special li:first-child { + padding-left: 0; + } + + ul.actions.stacked { + -moz-flex-direction: column; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + margin-left: 0; + } + + ul.actions.stacked li { + padding: 1.3em 0 0 0; + } + + ul.actions.stacked li:first-child { + padding-top: 0; + } + + ul.actions.fit { + width: calc(100% + 1em); + } + + ul.actions.fit li { + -moz-flex-grow: 1; + -webkit-flex-grow: 1; + -ms-flex-grow: 1; + flex-grow: 1; + -moz-flex-shrink: 1; + -webkit-flex-shrink: 1; + -ms-flex-shrink: 1; + flex-shrink: 1; + width: 100%; + } + + ul.actions.fit li > * { + width: 100%; + } + + ul.actions.fit.stacked { + width: 100%; + } + + @media screen and (max-width: 480px) { + + ul.actions:not(.fixed) { + -moz-flex-direction: column; + -webkit-flex-direction: column; + -ms-flex-direction: column; + flex-direction: column; + margin-left: 0; + width: 100% !important; + } + + ul.actions:not(.fixed) li { + -moz-flex-grow: 1; + -webkit-flex-grow: 1; + -ms-flex-grow: 1; + flex-grow: 1; + -moz-flex-shrink: 1; + -webkit-flex-shrink: 1; + -ms-flex-shrink: 1; + flex-shrink: 1; + padding: 1em 0 0 0; + text-align: center; + width: 100%; + } + + ul.actions:not(.fixed) li > * { + width: 100%; + } + + ul.actions:not(.fixed) li:first-child { + padding-top: 0; + } + + ul.actions:not(.fixed) li input[type="submit"], + ul.actions:not(.fixed) li input[type="reset"], + ul.actions:not(.fixed) li input[type="button"], + ul.actions:not(.fixed) li button, + ul.actions:not(.fixed) li .button { + width: 100%; + } + + ul.actions:not(.fixed) li input[type="submit"].icon:before, + ul.actions:not(.fixed) li input[type="reset"].icon:before, + ul.actions:not(.fixed) li input[type="button"].icon:before, + ul.actions:not(.fixed) li button.icon:before, + ul.actions:not(.fixed) li .button.icon:before { + margin-left: -0.5em; + } + + } + +/* Icons */ + + ul.icons { + cursor: default; + list-style: none; + padding-left: 0; + } + + ul.icons li { + display: inline-block; + padding: 0 1em 0 0; + } + + ul.icons li:last-child { + padding-right: 0; + } + + ul.icons li .icon:before { + font-size: 1.5em; + } + +/* Table */ + + .table-wrapper { + -webkit-overflow-scrolling: touch; + overflow-x: auto; + } + + table { + margin: 0 0 2em 0; + width: 100%; + } + + table tbody tr { + border: solid 1px #eee; + border-left: 0; + border-right: 0; + } + + table tbody tr:nth-child(2n + 1) { + background-color: rgba(144, 144, 144, 0.05); + } + + table td { + padding: 0.75em 0.75em; + } + + table th { + color: #2a2a2a; + font-size: 0.9em; + font-weight: 600; + padding: 0 0.75em 0.75em 0.75em; + text-align: left; + } + + table thead { + border-bottom: solid 2px #eee; + } + + table tfoot { + border-top: solid 2px #eee; + } + + table.alt { + border-collapse: separate; + } + + table.alt tbody tr td { + border: solid 1px #eee; + border-left-width: 0; + border-top-width: 0; + } + + table.alt tbody tr td:first-child { + border-left-width: 1px; + } + + table.alt tbody tr:first-child td { + border-top-width: 1px; + } + + table.alt thead { + border-bottom: 0; + } + + table.alt tfoot { + border-top: 0; + } + +/* Button */ + + input[type="submit"], + input[type="reset"], + input[type="button"], + button, + .button { + -moz-appearance: none; + -webkit-appearance: none; + -ms-appearance: none; + appearance: none; + -moz-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; + -webkit-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; + -ms-transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; + transition: background-color 0.2s ease-in-out, color 0.2s ease-in-out; + background-color: #1d2726; + border-radius: 0; + border: 0; + color: #ffffff !important; + cursor: pointer; + display: inline-block; + font-size: 0.9em; + font-weight: 600; + height: 4em; + letter-spacing: 0.125em; + line-height: 4em; + overflow: hidden; + padding: 0 2.75em; + text-align: center; + text-decoration: none; + text-overflow: ellipsis; + text-transform: uppercase; + white-space: nowrap; + } + + input[type="submit"]:hover, + input[type="reset"]:hover, + input[type="button"]:hover, + button:hover, + .button:hover { + background-color: #283634; + } + + input[type="submit"]:active, + input[type="reset"]:active, + input[type="button"]:active, + button:active, + .button:active { + background-color: #121818; + } + + input[type="submit"].icon, + input[type="reset"].icon, + input[type="button"].icon, + button.icon, + .button.icon { + padding-left: 2.35em; + } + + input[type="submit"].icon:before, + input[type="reset"].icon:before, + input[type="button"].icon:before, + button.icon:before, + .button.icon:before { + margin-right: 0.5em; + } + + input[type="submit"].fit, + input[type="reset"].fit, + input[type="button"].fit, + button.fit, + .button.fit { + width: 100%; + } + + input[type="submit"].small, + input[type="reset"].small, + input[type="button"].small, + button.small, + .button.small { + font-size: 0.8em; + height: 3.5em; + line-height: 3.5em; + padding: 0 2em; + } + + input[type="submit"].large, + input[type="reset"].large, + input[type="button"].large, + button.large, + .button.large { + font-size: 1.25em; + height: 3.15em; + line-height: 3.15em; + } + + input[type="submit"].alt, + input[type="reset"].alt, + input[type="button"].alt, + button.alt, + .button.alt { + background-color: transparent; + box-shadow: inset 0 0 0 2px #eee; + color: #2a2a2a !important; + } + + input[type="submit"].alt:hover, + input[type="reset"].alt:hover, + input[type="button"].alt:hover, + button.alt:hover, + .button.alt:hover { + background-color: rgba(144, 144, 144, 0.05); + } + + input[type="submit"].alt:active, + input[type="reset"].alt:active, + input[type="button"].alt:active, + button.alt:active, + .button.alt:active { + background-color: rgba(144, 144, 144, 0.1); + } + + input[type="submit"].alt.icon:before, + input[type="reset"].alt.icon:before, + input[type="button"].alt.icon:before, + button.alt.icon:before, + .button.alt.icon:before { + color: #bbb; + } + + input[type="submit"].primary, + input[type="reset"].primary, + input[type="button"].primary, + button.primary, + .button.primary { + background-color: #51c5e8; + color: #ffffff !important; + } + + input[type="submit"].primary:hover, + input[type="reset"].primary:hover, + input[type="button"].primary:hover, + button.primary:hover, + .button.primary:hover { + background-color: #68cdeb; + } + + input[type="submit"].primary:active, + input[type="reset"].primary:active, + input[type="button"].primary:active, + button.primary:active, + .button.primary:active { + background-color: #3abde5; + } + + input[type="submit"].disabled, input[type="submit"]:disabled, + input[type="reset"].disabled, + input[type="reset"]:disabled, + input[type="button"].disabled, + input[type="button"]:disabled, + button.disabled, + button:disabled, + .button.disabled, + .button:disabled { + background-color: #4a4a4a !important; + box-shadow: inset 0 -0.15em 0 0 rgba(0, 0, 0, 0.15); + color: #fff !important; + cursor: default; + opacity: 0.25; + } + +/* Feature */ + + .feature { + margin: 0 0 2em 0; + padding: 2em 0 2em 9em; + position: relative; + } + + .feature i { + border-radius: 100%; + border: solid 3px #eee; + color: #51c5e8; + display: block; + height: 7em; + left: 0; + line-height: 7.25em; + margin-top: -0.5em; + position: absolute; + text-align: center; + top: 2em; + width: 7em; + } + + .feature i:before { + font-size: 40px; + } + + .feature h3 { + margin-bottom: 0.5em; + } + + .feature p { + margin-bottom: 0; + } + +/* Image Feature */ + + .image-feature { + position: relative; + text-align: center; + margin: 0 0.5em 2em 0.5em; + } + + .image-feature > a { + display: block; + } + + .image-feature header { + background: #fff; + left: 0; + padding: 1.5em 0; + position: absolute; + top: 0; + width: 100%; + z-index: 1; + } + + .image-feature header:before { + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background: #fff; + bottom: -0.5em; + content: ''; + display: block; + height: 1em; + left: 50%; + margin-left: -0.5em; + position: absolute; + width: 1em; + } + + .image-feature header h3 { + font-size: 1em; + margin: 0; + } + + .image-feature .image { + margin: 0; + } + + .image-feature p { + margin: 0; + } + + .image-feature footer { + background: #1d2726; + background: rgba(29, 39, 38, 0.9); + bottom: 0; + color: #fff; + left: 0; + padding: 1.5em; + position: absolute; + width: 100%; + z-index: 1; + } + + .image-feature footer input[type="submit"].alt, + .image-feature footer input[type="reset"].alt, + .image-feature footer input[type="button"].alt, + .image-feature footer button.alt, + .image-feature footer .button.alt { + box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.25); + color: #fff !important; + } + +/* Wrapper */ + + .wrapper { + padding: 4em 0 4em 0; + } + + .wrapper.style1 { + background: #fff; + color: #888; + } + + .wrapper.style1 h2, .wrapper.style1 h3, .wrapper.style1 strong { + color: #5f7d6e; + } + + .wrapper.style1 header.major:after { + background: rgba(0, 0, 0, 0.25); + } + + .wrapper.style2 { + background: #eef5f1; + } + + .wrapper.style3 { + background: #f4f4f4; + } + + .wrapper.style4 { + background: #fafafa; + } + +/* Header */ + + +.logo-kep{ + width: 70px; + line-height: 5em; + vertical-align: middle; + margin-left: 5em; +} + +.booktime{ + background-color: #9bbfae; + color: #000; + margin-left:2em; + padding-left: 10px; + padding-right: 10px; +} + + #page-wrapper { + padding-top: 3em; + } + + #header { + background: #fff; + color: #000; + cursor: default; + height: 5em; + left: 0; + line-height: 5em; + position: fixed; + top: 0; + width: 100%; + z-index: 10000; + } + + #header h1 { + color: #9bbfae; + height: inherit; + left: 10.5em; + line-height: inherit; + margin: 0; + padding: 0; + position: absolute; + top: 0; + } + + #header h1 a { + font-size: 1em; + } + + #header h1 a:before { + color: #51c5e8; + margin-right: 0.35em; + } + + #header nav { + font-weight: 600; + height: inherit; + letter-spacing: 0.125em; + line-height: inherit; + position: absolute; + right: 1.5em; + text-transform: uppercase; + top: 0; + vertical-align: middle; + } + + #header nav > ul { + list-style: none; + line-height: 4.5em; + margin: 0; + padding-left: 0; + vertical-align: middle; + } + + #header nav > ul > li { + display: inline-block; + margin-left: 2em; + /*padding-left: 0;*/ + } + + #header nav > ul > li.booktime { + margin-right: 2em; + } + + #header nav > ul > li.socialmedia-menu { + color: #fff; + font-size: 30px; + margin: auto 0 auto 0; +} + + + #header nav > ul > li.language { + padding-left: 0; + margin: 0; + font-size: 12px; + color: #888; + -moz-transition: color 0.2s ease-in-out; + -webkit-transition: color 0.2s ease-in-out; + -ms-transition: color 0.2s ease-in-out; + transition: color 0.2s ease-in-out; + } + #header nav > ul > li.language:hover { + color: #fff; + } + + #header nav > ul > li a { + -moz-transition: color 0.2s ease-in-out; + -webkit-transition: color 0.2s ease-in-out; + -ms-transition: color 0.2s ease-in-out; + transition: color 0.2s ease-in-out; + color: #9bbfae; + display: inline-block; + text-decoration: none; + font-size: 0.8em; + } + + #header nav > ul > li a:before { + margin-right: 0.5em; + /*opacity: 0.5;*/ + } + + #header nav > ul > li a:hover { + color: #5f7d6e; + } + #header nav > ul > li.booktime a { + color: #fff; + } + + #header nav > ul > li.booktime a:hover { + color: #5f7d6e; + } + + #header nav > ul > li.active > a { + color: #fff; + } + + #header nav > ul > li:first-child { + margin-left: 0; + } + + #header nav > ul > li input[type="submit"], + #header nav > ul > li input[type="reset"], + #header nav > ul > li input[type="button"], + #header nav > ul > li button, + #header nav > ul > li .button { + font-size: 1em; + height: 2.25em; + line-height: 2.25em; + margin-bottom: 0; + padding: 0 1em; + position: relative; + top: -0.125em; + vertical-align: middle; + } + + #header nav > ul > li > ul { + display: none; + } + + #header .container { + position: relative; + } + + #header .container h1 { + left: 0; + } + + #header .container nav { + right: 0; + } + + .dropotron { + background: #1d2726; + color: #fff; + font-weight: 600; + letter-spacing: 0.125em; + list-style: none; + margin-top: -1em; + min-width: 14em; + padding: 1.25em 0; + text-transform: uppercase; + } + + .dropotron.level-0 { + font-size: 0.8em; + margin-top: 1em; + } + + .dropotron.level-0:before { + -moz-transform: rotate(45deg); + -webkit-transform: rotate(45deg); + -ms-transform: rotate(45deg); + transform: rotate(45deg); + background: #1d2726; + content: ''; + display: block; + height: 1em; + position: absolute; + right: 1.5em; + top: -0.5em; + width: 1em; + } + + .dropotron li > a { + -moz-transition: color 0.2s ease-in-out; + -webkit-transition: color 0.2s ease-in-out; + -ms-transition: color 0.2s ease-in-out; + transition: color 0.2s ease-in-out; + color: #ccc; + display: block; + padding: 0.125em 1.25em; + text-decoration: none; + } + + .dropotron li > a:hover { + color: #fff; + } + + .dropotron li.active > a { + color: #fff; + } + +/* Banner */ + + #banner { + background-color: #fff; + /*background-attachment: scroll, fixed; + background-image: url("images/overlay.png"), url("../../images/banner.jpg"); + background-position: top left, center center; + background-repeat: no-repeat; + background-size: cover; + background-size: auto, cover;*/ + color: #fff; + padding: 0em 0; + position: relative; + text-align: center; + } + + #banner img{ + width: 100%; + } + + #banner:after { + -moz-transition: opacity 1s ease-in-out; + -webkit-transition: opacity 1s ease-in-out; + -ms-transition: opacity 1s ease-in-out; + transition: opacity 1s ease-in-out; + background: #313148; + content: ''; + height: 100%; + left: 0; + opacity: 0; + position: absolute; + top: 0; + width: 100%; + z-index: 1; + } + + #banner :last-child { + margin-bottom: 0; + } + + #banner .inner { + background: #1d2726; + background: rgba(29, 39, 38, 0.9); + display: inline-block; + padding: 3.5em 5em; + position: relative; + z-index: 2; + } + + #banner .inner h2 { + color: inherit; + font-size: 2.5em; + line-height: 1.35em; + margin: 0; + padding: 0; + } + + #banner .inner p { + font-size: 1em; + letter-spacing: 0.125em; + line-height: 2em; + margin: 2em 0 0 0; + padding: 2em 0 0 0; + position: relative; + text-transform: uppercase; + top: -0.25em; + } + + #banner .inner p:before { + background: #888; + background: rgba(255, 255, 255, 0.25); + content: ''; + display: block; + height: 3px; + left: 50%; + margin: 0 0 0 -3em; + position: absolute; + top: 0; + width: 6em; + } + + #banner .inner .actions { + margin-top: 2em; + } + + #banner .inner input[type="submit"].alt, + #banner .inner input[type="reset"].alt, + #banner .inner input[type="button"].alt, + #banner .inner button.alt, + #banner .inner .button.alt { + box-shadow: inset 0 0 0 2px rgba(255, 255, 255, 0.25); + color: #fff !important; + } + + body.is-preload #banner:after { + opacity: 1.0; + } + +/* Mobile booking */ + +#mobil-menu{ + display: none; + background: #9bbfae; +} + +.mobil-menucont ul { + list-style: none; + font-size: 1em; + line-height: 4.5em; + text-align: center; + margin: 0; + padding-left: 0; + vertical-align: middle; +} + +.mobil-menucont ul li{ + color: #fff; + margin: 0 0.5em; + display: inline; +} + +.mobil-menucont ul li a{ + color: #fff; + text-decoration: none; + } + +.mobil-menucont ul .mobil-menu-book{ + margin-right: 2em; + padding: 0.5em 0.5em; + font-weight: 600; +} + + + @media screen and (max-width: 980px) { + #mobil-menu{ + display: block; + } +} + +/* Main */ + + #main > header.major { + margin: 0 0 2em 0; + text-align: center; + } + + header.major h2{ + font-family: 'Abril Fatface', cursive; + color: #5f7d6e; + font-size: 50px; + font-weight: 400; + margin-bottom: 0; + } + + + header.major h4{ + font-size: 13px; + color: #888888; + } + +.one-half { + /*float: left;*/ + width: 100%; + padding: 0 0px; + position: relative; + text-align: left; + overflow: hidden; +} + +.prices-category{ + /*font-family: 'Abril Fatface', cursive;*/ + text-align: center; + margin-top: 2em; + margin-bottom: 2em; +} + +.prices-category h4{ + color: #9bbfae; +} + + +.category-content { + position: relative; + display: inline-block; + width: 100%; + padding-bottom: 15px; + padding-top: 18px; + border-bottom: 1px solid #3d3d3d;} + +.category-text-left { + float: left; +} + +.main-title a { + text-decoration: none; + font-size: 16px; + color: #5f7d6e; + font-weight: 500; + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + transition: all 0.5s ease; +} + +.sub-title { + font-size: 13px; + color: #888888; + font-weight: 400; + padding-right: 40px; + letter-spacing: 0.6px; +} + +.category-text-right { + float: right; +} + +.category-content .price { + font-size: 16px; + color: #9bbfae; + letter-spacing: 0.5px; + font-weight: 600; + display: inline-block; + -webkit-transition: all 0.5s ease; + -moz-transition: all 0.5s ease; + -o-transition: all 0.5s ease; + transition: all 0.5s ease; +} + + +.common-home .box-heading:before, .box.related .box-heading:before { + content: ""; + background: url(../../assets/img/massage/ollo_rose.png) no-repeat scroll; + width: 75px; + height: 65px; + position: absolute; + top: 0; + left: 0; + right: 0; + margin: 0 auto; + display: inline-block; +} + + +/* About Us */ + +#aboutus .container .inner .dolgozo-nev{ + width: 100%; + text-align: center; + margin-bottom: 2em; +} +#aboutus .container .inner hr { + margin: 4em auto 4em auto; + border-bottom: solid 3px #888; + width: 40%; +} + +#aboutus .container .inner .dolgozo-nev .dolgozo-kep{ + width: 150px; + border-radius: 50%; + margin-bottom: 1em; +} + +#aboutus .container .inner .dolgozo-nev h4{ + color: #9bbfae; +} + +#aboutus .container .inner .dolgozo-nev h4 span{ + color: #888; +} + +.bemutatkozas, .bemutatkozas-dolgozo{ + display: flex; + width: 100%; +} + +.bemutatkozas-dolgozo{ + /* Gutter between the bio and the photo strip. `gap` is flex-only, so it + self-disables at <=480px where this container becomes display:block. */ + gap: 2.5em; + /* Default stretch + `margin:auto` on the strip centred it vertically, so + the photos floated in the middle of the bio instead of starting level + with the first line. */ + align-items: flex-start; +} + +.bem-bal, .bem-dolgozo-bal{ + width: 60%; +} + +.bem-dolgozo-bal{ + /* 50% + 46% + 2.5em gutter fits inside 100%. The shared 60% above plus a + 46% strip would overflow, and flex would shrink both to compensate - + which is what closed the gap between text and photos. */ + width: 50%; +} + +.bem-bal h4{ + color: #fff; +} + +.bem-bal p{ + text-align: justify; +} + +.bem-dolgozo-bal p{ + text-align: justify; + font-style: italic; + +} + +.bem-jobb{ + width: 40%; + margin-left: 4em; + float: right; +} + +.bem-jobb img{ + width: 100%; +} + +.bem-jobb h4{ + margin-top: 1em; + text-align: center; + color: #9bbfae; +} + +.bem-jobb h4 span{ + color: #888; +} +.bem-dolgozo-jobb{ + /* Wider than barber/beauty (40%): this vertical has 4 photos rather than + 6, so the narrower strip left them undersized next to the bio. */ + width: 46%; + float: right; + text-align: right; + /* was `margin: auto` - on a flex item that centres it on BOTH axes. */ + margin: 0; + +} + +.bem-dolgozo-jobb img{ + opacity: 0.8; + /* NB: the original declared `margin-right: 10ox` - an invalid unit, so the + right margin never applied. Corrected to a symmetric pair. */ + margin-left: 5px; + margin-right: 5px; + margin-bottom: 10px; + width: 47%; + transition: opacity .25s ease-in-out; + -moz-transition: opacity .25s ease-in-out; + -webkit-transition: opacity .25s ease-in-out; +} + +.bem-dolgozo-jobb img:hover{ + opacity: 1; + +} + + + +/* Treatments - Our Services */ + + #treatments{ + clear: both; + } + + .service-block { + width: 100%; + display: block; + } + + + .service-area .one-third { + + width: 28%; + padding: 0; + overflow: hidden; + } + + .service-area .one-third.service-part1, .service-area .one-third.service-part4 { + width: 30%; + padding: 0; + float: left; + overflow: hidden; + } + + .service-area .one-third.service-part2 { + width: 42%; + /*margin-top:30px; + margin-bottom: 30px;*/ + padding: 0 30px; + float:left; + overflow: hidden; + } + + .service-area .one-third .service img { + -webkit-transform: rotateY(0deg); + -moz-transform: rotateY(0deg); + -ms-transform: rotateY(0deg); + -o-transform: rotateY(0deg); + transform: rotateY(0deg); + transition: all 0.9s ease 0s; + -webkit-transition: all 0.9s ease 0s; + -moz-transition: all 0.9s ease 0s; + -ms-transition: all 0.9s ease 0s; + -o-transition: all 0.9s ease 0s; + max-width: 100%; + } + + .service-area .one-third .service:hover img { + -webkit-transform: rotateY(-180deg); + -moz-transform: rotateY(-180deg); + -ms-transform: rotateY(-180deg); + -o-transform: rotateY(-180deg); + transform: rotateY(-180deg); + transition: all 0.9s ease 0s; + -webkit-transition: all 0.9s ease 0s; + -moz-transition: all 0.9s ease 0s; + -ms-transition: all 0.9s ease 0s; + -o-transition: all 0.9s ease 0s; + } + + + .service-area .one-third .service .service-text .service-icon { + background: url(../../assets/img/service-icon.png) no-repeat scroll; + float: right; + height: 80px; + border-radius: 50%; + width: 80px; + position: relative; + background-color: #fff; + display: inline-block; + transition: all 0.5s ease 0s; + -webkit-transition: all 0.5s ease 0s; + -moz-transition: all 0.5s ease 0s; + -ms-transition: all 0.5s ease 0s; + -o-transition: all 0.5s ease 0s; + } + + .service .service-text .service-content .main-title { + font-size: 14px; + font-weight: 600; + color: #9bbfae; + text-transform: capitalize; + letter-spacing: 0.8px; + padding-bottom: 10px; + } + + .service .service-text .service-content .sub-title-serv { + font-size: 13px; + color: #888888; + font-weight: 400; + line-height: 20px; + letter-spacing: 0.6px; + } + + .service-area .one-third .service .service-text .service-content { + margin-right: 100px; + } + + .service-area .one-third .service .service-text { + padding: 0 0px 95px; + text-align: right; + } + + .service-area .one-third .service.service3 .service-text .service-content { + margin-right: 0px; + margin-left: 100px; + } + + .service-area .one-third .service.service3 .service-text { + text-align: left; + } + + + + .service-area .one-third .service.service3 .service-text .service-icon { + float: left; + } + + .service-area .one-third .service .service-text .service-icon.icon1 { + background-position: center -65px; + } + .service-area .one-third .service .service-text:hover .service-icon.icon1 { + background-position:center 22px; + } + .service-area .one-third .service .service-text .service-icon.icon2 { + background-position:center -417px; + } + .service-area .one-third .service .service-text:hover .service-icon.icon2 { + background-position: center -330px; + } + .service-area .one-third .service .service-text .service-icon.icon3 { + background-position:center -765px; + } + .service-area .one-third .service .service-text:hover .service-icon.icon3 { + background-position: center -678px; + } + .service-area .one-third .service.service3 .service-text .service-icon.icon1 { + background-position: center -242px; + } + .service-area .one-third .service.service3 .service-text:hover .service-icon.icon1 { + background-position: center -155px; + } + .service-area .one-third .service.service3 .service-text .service-icon.icon2 { + background-position: center -590px; + } + .service-area .one-third .service.service3 .service-text:hover .service-icon.icon2 { + background-position:center -503px; + } + .service-area .one-third.service-part3 .service .service-text .service-icon.icon3 { + background-position: center -939px; + } + .service-area .one-third.service-part3 .service .service-text:hover .service-icon.icon3 { + background-position:center -852px; + } + .service-area .one-third .service .service-text:hover .service-content .main-title { + color: #5f7d6e; + } + + + +/* One */ + + #one { + padding-top:10em; + padding-bottom: 5em; + } + + #one p { + font-size: 1.25em; + line-height: 1.75em; + margin: -0.5em 0 0 0; + } + + #one .inner { + display: inline-block; + margin-top: -6em; + padding-right: 6em; + vertical-align: middle; + width: 100%; + } + + #one .device { + border: solid 40px #fff; + border-bottom: 0; + border-radius: 20px; + border-bottom-left-radius: 0; + border-bottom-right-radius: 0; + display: inline-block; + margin-bottom: 0; + position: relative; + vertical-align: middle; + width: 100%; + } + + #one .device:after { + background: #eee; + border-radius: 100%; + content: ''; + height: 10px; + left: 50%; + margin: -23px 0 0 -5px; + position: absolute; + top: 0; + width: 10px; + } + + +/*two */ + + .nyitvatartas .about-content { + position: relative; + display: inline-block; + width: 100%; + padding: 25px 0px 15px; + border-bottom: 1px solid #3d3d3d; + font-size: 16px; + font-weight: 600; + } + + .nyitvatartas .title, .nyitvatartas .time{ + color: #888; + } + + .closed{ + color: rgba(228,173,156,0.6); + } + + .nyitvatartas .about-content .about-text-left { + float: left; + } + + .nyitvatartas .about-content .about-text-right { + float: right; +} + + + +/* Footer */ + +#footer .footer-div{ + display: flex; +} + + +#footer .footer-div .companyname{ + color: #fff; + font-family: 'Abril Fatface', cursive; + font-size: 24px; + margin-bottom: 20px; +} + +#footer .footer-div .companyname span{ + border-bottom: solid 5px #fff; + } + +#footer .footer-div ul{ + margin: 0; + list-style: none; +} + +#footer .footer-logo{ + max-width: 100px; +} + + #footer { + background: #5f7d6e; + color: #fff; + padding: 2em 0 0 0; + text-align: center; + } + + #footer .icons { + font-size: 24px; + margin: 0 0 1em 0; + padding-left: 0; + } + + #footer .icons li { + margin-top: 0.5em; + font-size: 16px; + padding: 0 1em 0 1em; + } + + #footer .icons li:last-child { + padding-right: 0; + } + + #footer .icons li a { + -moz-transition: color 0.2s ease-in-out; + -webkit-transition: color 0.2s ease-in-out; + -ms-transition: color 0.2s ease-in-out; + transition: color 0.2s ease-in-out; + color: #fff; + } + + #footer .icons li a:hover { + color: #9bbfae; + } + + .footer-bal{ + margin: 0 10px; + width: 30%; + + } + + .footer-bal .contact-block a{ + text-decoration: none; + color: #fff; +} + + + .footer-jobb{ + width: 60%; + margin:0; + float: left; + } + + .footer-jobb img{ + width: 100%; + margin:0; + } + + + #footer .copyright { + color: #888; + background: #fff; + font-size: 0.9em; + padding: 1em 0 1em 0; + margin: 2em 0 0 0; + } + + .ui-widget-content { + border: 1px solid #cfe0d6!important; + background: #f3f8f5!important; + color: #333333!important; + } + + .ui-state-default, .ui-widget-content .ui-state-default, .ui-widget-header .ui-state-default, .ui-button, html .ui-button.ui-state-disabled:hover, html .ui-button.ui-state-disabled:active { + border: 1px solid #cfe0d6!important; + background: #9bbfae url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x; + font-weight: bold!important; + color: #5f7d6e!important; + } + + table thead { + border-bottom: solid 2px #9bbfae!important; + } + + .ui-widget-header { + border: 1px solid #5f7d6e!important; + background: #9bbfae!important; + } + + .widget-content .ui-state-highlight, .ui-widget-header .ui-state-highlight { + border: 1px solid #9bbfae!important; + background: #cfe0d6!important; + } + + + + .ui-state-active, .ui-widget-content .ui-state-active, .ui-widget-header .ui-state-active, a.ui-button:active, .ui-button:active, .ui-button.ui-state-active:hover { + border: 1px solid #5f7d6e; + background: #ffffff url(images/ui-bg_glass_65_ffffff_1x400.png) 50% 50% repeat-x; + } + + .ui-state-active{ + background: #9bbfae url(images/ui-bg_glass_100_f6f6f6_1x400.png) 50% 50% repeat-x!important; + } + + table tbody tr { + border: solid 1px #cfe0d6!important; + border-left: 0; + border-right: 0; + } + + table tbody tr:nth-child(2n + 1) { + background-color: rgb(255 244 241)!important; + } + + .ui-state-highlight{ + border: 1px solid #5f7d6e!important; + background: #cfe0d6!important; + } + + + + #datepicker table tr td:last-child a{ + background-image: none!important; + /*background-color: rgb(173, 40, 40)!important;*/ + color:red!important; + } + + .ui-state-disabled { + cursor: default !important; + pointer-events: none; + opacity: 0!important; + + } + + +/* Wide */ + + @media screen and (max-width: 1680px) { + + /* Basic */ + + body, input, select, textarea { + font-size: 11pt; + } + + } + +/* Normal */ + + @media screen and (max-width: 1280px) { + + /* Basic */ + + body, input, select, textarea { + font-size: 11pt; + } + + /* Feature */ + + .feature { + padding-left: 7em; + } + + .feature i { + height: 5.25em; + line-height: 5.5em; + margin-top: 0; + width: 5.25em; + } + + .feature i:before { + font-size: 32px; + } + + /* Wrapper */ + + .wrapper { + padding: 4em 0 2em 0; + } + + #header nav > ul > li{ + margin-left: 0.5em; + } + + /* Banner */ + + #banner { + background-attachment: scroll; + padding: 0 0; + } + + #banner .inner { + padding: 3em 4em; + } + + #banner .inner h2 { + font-size: 2.25em; + } + + /* Main */ + + #main { + padding: 2em 0; + } + + .service-area .one-third .service .service-text{ + padding: 0 0 45px; + } + + .main-title a {font-size: 14px;} + + .sub-title{font-size:10px;} + + + /* One */ + + #one .inner { + margin-top: -4em; + padding-right: 4em; + } + + #one .device { + border-width: 35px; + } + + #one .device:after { + margin-top: -22px; + } + + + #header { + display: none; + } + + #mobil-menu{ + display: block; + } + + + #navPanel, #navButton { + display: none; + } + + #page-wrapper { + padding-top: 0; + } + + + #mobil-menu{ + display: block; + } + + #header nav { + padding-left: 240px; + } + + #page-wrapper { + -moz-backface-visibility: hidden; + -webkit-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + -moz-transition: -moz-transform 0.5s ease; + -webkit-transition: -webkit-transform 0.5s ease; + -ms-transition: -ms-transform 0.5s ease; + transition: transform 0.5s ease; + padding-bottom: 1px; + } + + #navButton { + -moz-backface-visibility: hidden; + -webkit-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + -moz-transition: -moz-transform 0.5s ease; + -webkit-transition: -webkit-transform 0.5s ease; + -ms-transition: -ms-transform 0.5s ease; + transition: transform 0.5s ease; + display: block; + height: 4em; + left: 0; + position: fixed; + top: 0; + width: 6em; + z-index: 10001; + } + + #navButton .toggle { + text-decoration: none; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + } + + #navButton .toggle:before { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; + text-transform: none !important; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + } + + #navButton .toggle:before { + background: rgba(29, 39, 38, 0.75); + color: #ffffff; + content: '\f0c9'; + display: block; + font-size: 18px; + height: 2.35em; + left: 0.5em; + line-height: 2.35em; + position: absolute; + text-align: center; + top: 0.5em; + width: 3em; + } + + #navPanel { + -moz-backface-visibility: hidden; + -webkit-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + -moz-transform: translateX(-275px); + -webkit-transform: translateX(-275px); + -ms-transform: translateX(-275px); + transform: translateX(-275px); + -moz-transition: -moz-transform 0.5s ease; + -webkit-transition: -webkit-transform 0.5s ease; + -ms-transition: -ms-transform 0.5s ease; + transition: transform 0.5s ease; + background-image: -moz-linear-gradient(left, rgba(0,0,0,0) 95%, rgba(0,0,0,0.1)); + background-image: -webkit-linear-gradient(left, rgba(0,0,0,0) 95%, rgba(0,0,0,0.1)); + background-image: -ms-linear-gradient(left, rgba(0,0,0,0) 95%, rgba(0,0,0,0.1)); + background-image: linear-gradient(left, rgba(0,0,0,0) 95%, rgba(0,0,0,0.1)); + display: block; + height: 100%; + left: 0; + overflow-y: auto; + position: fixed; + top: 0; + width: 275px; + z-index: 10002; + background: #1d2726; + color: #c7c9c9; + padding: 0.25em 1.5em; + } + + #navPanel .link { + border-top: solid 1px rgba(255, 255, 255, 0.05); + color: #c7c9c9; + display: block; + font-size: 0.8em; + font-weight: 600; + height: 3.5em; + letter-spacing: 0.125em; + line-height: 3.5em; + text-decoration: none; + text-transform: uppercase; + } + + #navPanel .link:first-child { + border-top: 0; + } + + #navPanel .link.depth-0 { + color: #ffffff; + } + + #navPanel .link .indent-1 { + display: inline-block; + width: 1.25em; + } + + #navPanel .link .indent-2 { + display: inline-block; + width: 2.5em; + } + + #navPanel .link .indent-3 { + display: inline-block; + width: 3.75em; + } + + #navPanel .link .indent-4 { + display: inline-block; + width: 5em; + } + + #navPanel .link .indent-5 { + display: inline-block; + width: 6.25em; + } + + body.navPanel-visible #page-wrapper { + -moz-transform: translateX(275px); + -webkit-transform: translateX(275px); + -ms-transform: translateX(275px); + transform: translateX(275px); + } + + body.navPanel-visible #navButton { + -moz-transform: translateX(275px); + -webkit-transform: translateX(275px); + -ms-transform: translateX(275px); + transform: translateX(275px); + } + + body.navPanel-visible #navPanel { + -moz-transform: translateX(0); + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + } + +/* Narrow */ + + + + @media screen and (max-width: 980px) { + + /* Basic */ + + html, body { + overflow-x: hidden; + } + + body, input, select, textarea { + font-size: 12pt; + } + + h2 { + font-size: 1.25em; + line-height: 1.75em; + } + + h3 { + font-size: 1.1em; + line-height: 1.5em; + } + + h4 { + font-size: 1em; + line-height: 1.5em; + } + + /* Section/Article */ + + section.special, article.special { + text-align: center; + } + + header h2 + p { + font-size: 0.9em; + } + + header h3 + p { + font-size: 0.8em; + } + + header h4 + p, + header h5 + p, + header h6 + p { + font-size: 0.8em; + } + + /* Feature */ + + .feature { + padding-left: 9em; + } + + .feature i { + height: 7em; + line-height: 7.5em; + margin-top: -0.5em; + width: 7em; + } + + .feature i:before { + font-size: 42px; + } + + /* Image Feature */ + + .image-feature .image { + max-height: 30em; + overflow: hidden; + } + + /* Header */ + + #page-wrapper { + padding-top: 0; + } + + #header { + display: none; + } + + /* Banner */ + + #banner { + padding: 0em 0; + } + + #banner .inner { + padding: 3em; + } + + #banner .inner h2 { + font-size: 2em; + } + + + + /* Main */ + + #main > header.major { + margin: 0; + } + + .bem-jobb{margin-left:2em;} + + .bem-bal p, .bem-dolgozo-bal p {font-size:14px; margin: 0 0 0.5em 0;} + + .bem-dolgozo-jobb {width: 30%;} + + .bem-dolgozo-jobb img{width: 70%;} + + header.major h2 {font-size: 36px;} + + + /* One */ + + #one {padding-top: 2em;} + + #one p { + font-size: 1em; + } + + #one .inner { + display: block; + margin: 0 auto; + max-width: 100%; + padding: 0; + text-align: center; + width: 35em; + } + + #one .device { + display: block; + margin: 4em auto 0 auto; + max-width: 100%; + width: 30em; + } + + .service-area .one-third.service-part1{width: 50%; padding-right: 10px;} + .service-area .one-third.service-part2{display: none;} + .service-area .one-third.service-part3{width:50%;padding-left: 10px;} + + .one-half {float: none; width: 100%;} + #footer .footer-div{display:block;} + .footer-bal{width: 100%;} + .footer-jobb{width: 100%; float: none; margin-top:2em;} + + + /* Off-Canvas Navigation */ + + #page-wrapper { + -moz-backface-visibility: hidden; + -webkit-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + -moz-transition: -moz-transform 0.5s ease; + -webkit-transition: -webkit-transform 0.5s ease; + -ms-transition: -ms-transform 0.5s ease; + transition: transform 0.5s ease; + padding-bottom: 1px; + } + + #navButton { + -moz-backface-visibility: hidden; + -webkit-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + -moz-transition: -moz-transform 0.5s ease; + -webkit-transition: -webkit-transform 0.5s ease; + -ms-transition: -ms-transform 0.5s ease; + transition: transform 0.5s ease; + display: block; + height: 4em; + left: 0; + position: fixed; + top: 0; + width: 6em; + z-index: 10001; + } + + #navButton .toggle { + text-decoration: none; + height: 100%; + left: 0; + position: absolute; + top: 0; + width: 100%; + } + + #navButton .toggle:before { + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; + display: inline-block; + font-style: normal; + font-variant: normal; + text-rendering: auto; + line-height: 1; + text-transform: none !important; + font-family: 'Font Awesome 5 Free'; + font-weight: 900; + } + + #navButton .toggle:before { + background: rgba(29, 39, 38, 0.75); + color: #ffffff; + content: '\f0c9'; + display: block; + font-size: 18px; + height: 2.35em; + left: 0.5em; + line-height: 2.35em; + position: absolute; + text-align: center; + top: 0.5em; + width: 3em; + } + + #navPanel { + -moz-backface-visibility: hidden; + -webkit-backface-visibility: hidden; + -ms-backface-visibility: hidden; + backface-visibility: hidden; + -moz-transform: translateX(-275px); + -webkit-transform: translateX(-275px); + -ms-transform: translateX(-275px); + transform: translateX(-275px); + -moz-transition: -moz-transform 0.5s ease; + -webkit-transition: -webkit-transform 0.5s ease; + -ms-transition: -ms-transform 0.5s ease; + transition: transform 0.5s ease; + background-image: -moz-linear-gradient(left, rgba(0,0,0,0) 95%, rgba(0,0,0,0.1)); + background-image: -webkit-linear-gradient(left, rgba(0,0,0,0) 95%, rgba(0,0,0,0.1)); + background-image: -ms-linear-gradient(left, rgba(0,0,0,0) 95%, rgba(0,0,0,0.1)); + background-image: linear-gradient(left, rgba(0,0,0,0) 95%, rgba(0,0,0,0.1)); + display: block; + height: 100%; + left: 0; + overflow-y: auto; + position: fixed; + top: 0; + width: 275px; + z-index: 10002; + background: #1d2726; + color: #c7c9c9; + padding: 0.25em 1.5em; + } + + #navPanel .link { + border-top: solid 1px rgba(255, 255, 255, 0.05); + color: #c7c9c9; + display: block; + font-size: 0.8em; + font-weight: 600; + height: 3.5em; + letter-spacing: 0.125em; + line-height: 3.5em; + text-decoration: none; + text-transform: uppercase; + } + + #navPanel .link:first-child { + border-top: 0; + } + + #navPanel .link.depth-0 { + color: #ffffff; + } + + #navPanel .link .indent-1 { + display: inline-block; + width: 1.25em; + } + + #navPanel .link .indent-2 { + display: inline-block; + width: 2.5em; + } + + #navPanel .link .indent-3 { + display: inline-block; + width: 3.75em; + } + + #navPanel .link .indent-4 { + display: inline-block; + width: 5em; + } + + #navPanel .link .indent-5 { + display: inline-block; + width: 6.25em; + } + + body.navPanel-visible #page-wrapper { + -moz-transform: translateX(275px); + -webkit-transform: translateX(275px); + -ms-transform: translateX(275px); + transform: translateX(275px); + } + + body.navPanel-visible #navButton { + -moz-transform: translateX(275px); + -webkit-transform: translateX(275px); + -ms-transform: translateX(275px); + transform: translateX(275px); + } + + body.navPanel-visible #navPanel { + -moz-transform: translateX(0); + -webkit-transform: translateX(0); + -ms-transform: translateX(0); + transform: translateX(0); + } + + } + +/* Mobile */ + + @media screen and (max-width: 736px) { + + /* Basic */ + + body, input, select, textarea { + font-size: 12pt; + } + + /* Feature */ + + .feature { + margin: 0 auto 2em auto; + max-width: 100%; + padding: 0; + text-align: center; + width: 30em; + } + + .feature i { + left: 0; + margin: 0 auto 2em auto; + position: relative; + top: 0; + } + + /* Image Feature */ + + .image-feature { + margin: 0 auto 2em auto; + max-width: 100%; + width: 25em; + } + + .image-feature .image { + max-height: none; + } + + /* Wrapper */ + + .wrapper { + padding: 2.5em 1.25em 0.5em 1.25em; + } + + /* Banner */ + + #banner { + padding: 6em 0; + } + + #banner .inner { + padding: 2em 2.5em; + width: 100%; + } + + #banner .inner h2 { + font-size: 1.5em; + } + + #banner .inner p { + margin: 1.5em 0 0 0; + padding: 1.5em 0 0 0; + } + + /* One */ + + #one .device { + border-width: 20px; + margin-top: 2em; + max-width: 100%; + width: 25em; + } + + #one .device:after { + height: 8px; + margin: -13px 0 0 -4px; + width: 8px; + } + + .bookingContainer { + width: 95%!important; + margin-right: 10px!important; + margin-left: 10px!important; + } + + .bookingResultsWrapper { + width: 665px!important; + margin-left: 0!important; + } + + #bookingStep3 { + min-height: 720px!important; + } + + .serviceInfoCol { + padding-left: 5px!important; + } + + .serviceCol h4 { + margin-left: 0!important; + } + + .serviceNameCol { + padding-left: 0!important; + } + + .serviceTable { + width: 93%!important; + margin-left: 5px!important; + margin-right: 0!important; + padding: 0 5px!important; + margin-bottom: 100px!important; + } + + .bookingResultsWrapper { + width: 100%!important; + float: none!important; + } + + .bookingCalendar { + float: none!important; + } + + .availableTimes { + margin-bottom: 60px!important; + } + + .bookingContainer { + margin-top: -12px!important; + } + } + +/* Mobile (Portrait) */ + + @media screen and (max-width: 480px) { + + #banner{padding:0;} + .service-part2{display: none;} + .one-third{width: 100%;} + .service-area .one-third.service-part1{width: 100%;} + .service-area .one-third.service-part3{width: 100%;} + .service-area .one-third .service .service-text {text-align: center; padding-bottom: 30px;} + .service-area .one-third .service .service-text .service-icon {float: none;} + .service-area .one-third .service .service-text .service-content {margin: 0;} + .service .service-text .service-content .sub-title-serv {padding: 0 20px 0 20px;} + .service-area .one-third .service.service3 .service-text .service-icon {float: none;} + .service-area .one-third .service.service3 .service-text {text-align:center;} + .service-area .one-third .service.service3 .service-text .service-content {margin-left: 0;} + .service-area .one-third {width: 100%;} + + #one{padding-top: 3em;} + + .bemutatkozas{display: block;} + .bem-bal{width: 100%;} + .bem-jobb{width: 100%; margin-bottom: 2em;} + + .bemutatkozas-dolgozo{display: block;} + .bem-dolgozo-bal {width: 100%;} + .bem-dolgozo-jobb {width: 100%; float: none; text-align: center;} + + #czcategorylistblock .czcategorylist-wrapper .category_lists .one-half .category-content {width: 100%; } + .one-half{width:100%; float: none;} + + .category-text-left{float: none;} + .category-text-right {display: block; float: left;} + + #two {margin-bottom: 3em;} + header.major h2 {font-size: 32px;} + + #footer .footer-div {display: block;} + .footer-bal{width:100%;} + .footer-jobb{width:100%; float: none; margin-top:2em;} + + + /* Basic */ + + html, body { + min-width: 320px; + } + + body, input, select, textarea { + font-size: 12pt; + } + + h1 br, h2 br, h3 br, h4 br, h5 br, h6 br { + display: none; + } + + h2 { + font-size: 1.15em; + line-height: 1.75em; + } + + h3 { + font-size: 1em; + line-height: 1.5em; + } + + /* List */ + + ul.actions { + margin: 0 0 2em 0; + } + + ul.actions li { + display: block; + padding: 1em 0 0 0; + text-align: center; + width: 100%; + } + + ul.actions li:first-child { + padding-top: 0; + } + + ul.actions li > * { + margin: 0 !important; + width: 100%; + } + + ul.actions li > *.icon:before { + margin-left: -2em; + } + + ul.actions.small li { + padding: 0.5em 0 0 0; + } + + ul.actions.small li:first-child { + padding-top: 0; + } + + /* Button */ + + input[type="submit"], + input[type="reset"], + input[type="button"], + button, + .button { + padding: 0; + } + + /* One */ + + #one .device { + border-width: 15px; + } + + #one .device:after { + height: 5px; + margin: -10px 0 0 -2.5px; + width: 5px; + } + + } + + .priceSubTitle{ + font-size: 0.7em; + letter-spacing: initial; + margin-bottom: 0; + } + +/* ------------------------------------------------------------------ * + * Massage treatment icons. + * + * barber/beauty use a single vertical sprite (service-icon.png) and shift + * background-position per icon, with a second offset for the hover state. + * Massage ships six individual PNGs instead, so the sprite offsets are + * overridden back to centre and each icon gets its own image. Hover now + * tints the circle rather than swapping to a second sprite frame. + * + * Column 1 (.service) -> icon1, icon2, icon3 + * Column 3 (.service3) -> icon4, icon5, icon6 + * ------------------------------------------------------------------ */ + +.service-area .one-third .service .service-text .service-icon, +.service-area .one-third .service.service3 .service-text .service-icon { + background-image: none; + background-repeat: no-repeat; + background-position: center center; + background-size: 46px 46px; + background-color: #fff; + -webkit-transition: background-color 0.35s ease; + transition: background-color 0.35s ease; +} + +.service-area .one-third .service .service-text .service-icon.icon1, +.service-area .one-third .service .service-text:hover .service-icon.icon1 { + background-image: url(../../assets/img/massage/icon1.png); + background-position: center center; +} +.service-area .one-third .service .service-text .service-icon.icon2, +.service-area .one-third .service .service-text:hover .service-icon.icon2 { + background-image: url(../../assets/img/massage/icon2.png); + background-position: center center; +} +.service-area .one-third .service .service-text .service-icon.icon3, +.service-area .one-third .service .service-text:hover .service-icon.icon3 { + background-image: url(../../assets/img/massage/icon3.png); + background-position: center center; +} + +.service-area .one-third .service.service3 .service-text .service-icon.icon1, +.service-area .one-third .service.service3 .service-text:hover .service-icon.icon1 { + background-image: url(../../assets/img/massage/icon4.png); + background-position: center center; +} +.service-area .one-third .service.service3 .service-text .service-icon.icon2, +.service-area .one-third .service.service3 .service-text:hover .service-icon.icon2 { + background-image: url(../../assets/img/massage/icon5.png); + background-position: center center; +} +.service-area .one-third.service-part3 .service .service-text .service-icon.icon3, +.service-area .one-third.service-part3 .service .service-text:hover .service-icon.icon3 { + background-image: url(../../assets/img/massage/icon6.png); + background-position: center center; +} + +/* Hover tint, replacing the sprite's second frame. */ +.service-area .one-third .service .service-text:hover .service-icon, +.service-area .one-third .service.service3 .service-text:hover .service-icon { + background-color: #cfe0d6; +} + + +/* Treatment-type blurb, printed once per price-list group instead of once + per duration variant. + Typography deliberately matches .sub-title (the per-item description in the + barber/beauty price lists) so the three verticals read the same: 13px, + #888888, 0.6px tracking. .prices-category sets text-align:center for its + short heading, which has to be undone here - a full sentence centred over + several lines is what made this block look wrong. The box stays centred, + the text inside is left-aligned. */ +/* Specificity note: the Priser section carries id="one", and massage.css + inherits `#one p { font-size: 1.25em; line-height: 1.75em }` from the + original theme (line ~3469, repeated in a media query ~4111). That is + 1,0,1 and beats a two-class selector, which is why an earlier + `.prices-category .category-intro` rule was silently ignored and the blurb + rendered at roughly 26px. Leading with #one makes this 1,2,0 so it wins in + both the base and the media-query case, without !important. */ +#one .prices-category .category-intro, +#one p.category-intro { + /* Full width and flush left so the blurb's left edge lines up with the + service rows below it, the way .sub-title does in barber/beauty. + A centred max-width box sat visibly off-axis from the rows. */ + max-width: 100%; + margin: 0.6em 0 0 0; + text-align: left; + font-size: 13px; + line-height: 1.7; + letter-spacing: 0.6px; + color: #888888; + font-weight: 400; +} + +/* Note under the Priser heading explaining treatment time vs booked slot. + Inherits header.major h4 (13px / #888888); only the measure is constrained + so it does not run the full width of the section. */ +h4.prices-note { + max-width: 44em; + margin: 0.6em auto 0 auto; + line-height: 1.7; + font-weight: 400; + letter-spacing: 0.6px; +} + + +/* Single-service booking: rows other than the selected treatment are cleared + and disabled, so dim them to make it obvious they are out of play. Clicking + the selected row again releases everything. */ +.serviceRow.serviceRowDisabled{ + opacity: 0.38; + -webkit-transition: opacity 0.2s ease-in-out; + transition: opacity 0.2s ease-in-out; +} + +.serviceRow.serviceRowDisabled .serviceCheckCol input[type="checkbox"]{ + cursor: not-allowed; +} diff --git a/assets/img/massage.jpg b/assets/img/massage.jpg new file mode 100644 index 0000000..0447a18 Binary files /dev/null and b/assets/img/massage.jpg differ diff --git a/assets/img/massage/banner.jpg b/assets/img/massage/banner.jpg new file mode 100644 index 0000000..fb28212 Binary files /dev/null and b/assets/img/massage/banner.jpg differ diff --git a/assets/img/massage/icon1.png b/assets/img/massage/icon1.png new file mode 100644 index 0000000..51ecadc Binary files /dev/null and b/assets/img/massage/icon1.png differ diff --git a/assets/img/massage/icon2.png b/assets/img/massage/icon2.png new file mode 100644 index 0000000..8bd02bd Binary files /dev/null and b/assets/img/massage/icon2.png differ diff --git a/assets/img/massage/icon3.png b/assets/img/massage/icon3.png new file mode 100644 index 0000000..192f3a1 Binary files /dev/null and b/assets/img/massage/icon3.png differ diff --git a/assets/img/massage/icon4.png b/assets/img/massage/icon4.png new file mode 100644 index 0000000..0828e84 Binary files /dev/null and b/assets/img/massage/icon4.png differ diff --git a/assets/img/massage/icon5.png b/assets/img/massage/icon5.png new file mode 100644 index 0000000..233da06 Binary files /dev/null and b/assets/img/massage/icon5.png differ diff --git a/assets/img/massage/icon6.png b/assets/img/massage/icon6.png new file mode 100644 index 0000000..a87aa37 Binary files /dev/null and b/assets/img/massage/icon6.png differ diff --git a/assets/img/massage/map.jpg b/assets/img/massage/map.jpg new file mode 100755 index 0000000..db8551c Binary files /dev/null and b/assets/img/massage/map.jpg differ diff --git a/assets/img/massage/ollo_rose.png b/assets/img/massage/ollo_rose.png new file mode 100755 index 0000000..4b4b99b Binary files /dev/null and b/assets/img/massage/ollo_rose.png differ diff --git a/assets/img/massage/vincent/1.jpg b/assets/img/massage/vincent/1.jpg new file mode 100644 index 0000000..c471da4 Binary files /dev/null and b/assets/img/massage/vincent/1.jpg differ diff --git a/assets/img/massage/vincent/2.jpg b/assets/img/massage/vincent/2.jpg new file mode 100644 index 0000000..ede65c5 Binary files /dev/null and b/assets/img/massage/vincent/2.jpg differ diff --git a/assets/img/massage/vincent/3.jpg b/assets/img/massage/vincent/3.jpg new file mode 100644 index 0000000..aa577f3 Binary files /dev/null and b/assets/img/massage/vincent/3.jpg differ diff --git a/assets/img/massage/vincent/4.jpg b/assets/img/massage/vincent/4.jpg new file mode 100644 index 0000000..0444511 Binary files /dev/null and b/assets/img/massage/vincent/4.jpg differ diff --git a/assets/img/massage/vincent/vincent.jpg b/assets/img/massage/vincent/vincent.jpg new file mode 100644 index 0000000..d6b1b1f Binary files /dev/null and b/assets/img/massage/vincent/vincent.jpg differ diff --git a/assets/img/service-banner-massage.png b/assets/img/service-banner-massage.png new file mode 100644 index 0000000..f019db0 Binary files /dev/null and b/assets/img/service-banner-massage.png differ diff --git a/assets/img/worker_Vincent_Pusch.jpg b/assets/img/worker_Vincent_Pusch.jpg new file mode 100644 index 0000000..0f7cc2f Binary files /dev/null and b/assets/img/worker_Vincent_Pusch.jpg differ diff --git a/documents/massage_real_catalogue.sql b/documents/massage_real_catalogue.sql new file mode 100644 index 0000000..7a7e14d --- /dev/null +++ b/documents/massage_real_catalogue.sql @@ -0,0 +1,73 @@ +-- ============================================================================ +-- Massage vertical - REAL catalogue and therapist (replaces the demo data) +-- ============================================================================ +-- Studio Beve, 2026-08-13. +-- +-- DISPLAY vs BOOKING DURATION +-- --------------------------- +-- The guest-facing duration is in the service NAME: 50 / 80 / 110 min. +-- services.service_time is the BOOKED slot: 60 / 90 / 120 min, which adds +-- changing and payment time. The two intentionally differ. +-- +-- service_time never reaches the guest: the booking form's duration input is +-- .serviceTime { display: none; }, and the price list, the booking form and +-- the confirmation email all render service_name. The email's +-- "start - finish" window correctly shows the full reserved slot. +-- +-- Prices: 50 min = 1050, 80 min = 1575, 110 min = 2050 NOK. +-- +-- Order matters: the landing-page price list groups by service_category and +-- only prints a heading when it changes, iterating service_id ASC. Rows are +-- therefore inserted grouped by treatment type. +-- ============================================================================ + +SET NAMES utf8mb4; + +SET @cat := (SELECT service_category_id FROM service_categories + WHERE serv_cat_slug = 'massage' AND is_deleted = 0 LIMIT 1); +SET @worker := (SELECT worker_id FROM workers + WHERE service_category_id = @cat AND is_deleted = 0 LIMIT 1); +SELECT @cat AS massage_category_id, @worker AS masseur_worker_id; + +-- ---------------------------------------------------------------- therapist +UPDATE workers + SET worker_name = 'Vincent Pusch', + worker_profile_img = 'worker_Vincent_Pusch.jpg' + WHERE worker_id = @worker; + +-- ---------------------------------------------------------------- catalogue +-- The four placeholder services are removed outright: no booking references +-- them (verified zero bookings for this worker), so there is nothing to +-- preserve with a soft delete. +DELETE FROM services WHERE service_category_id = @cat; + +INSERT INTO services ( + service_type, + service_category_no, service_category_en, service_category_hu, + service_name_no, service_name_en, service_name_hu, + service_description_no, service_description_en, service_description_hu, + service_price, service_time, service_category_id, is_enabled, is_deleted +) VALUES + ('massage', 'Klassisk svensk massasje', 'Classic Swedish Massage', 'Klasszikus svédmasszázs', 'Klassisk svensk massasje – 50 min', 'Classic Swedish Massage – 50 min', 'Klasszikus svédmasszázs – 50 perc', 'En behagelig helkroppsmassasje som bidrar til å løsne opp spenninger i muskulaturen, redusere hverdagens stress og fremme avslapning og velvære.', 'A relaxing full-body massage designed to ease muscle tension, reduce everyday stress and promote relaxation and well-being.', 'Kellemes, teljes testet átmozgató masszázs, amely segít ellazítani az izmokat, csökkenteni a mindennapi feszültséget és támogatja a testi-lelki kikapcsolódást.', 1050, '01:00:00', @cat, 1, 0), + ('massage', 'Klassisk svensk massasje', 'Classic Swedish Massage', 'Klasszikus svédmasszázs', 'Klassisk svensk massasje – 80 min', 'Classic Swedish Massage – 80 min', 'Klasszikus svédmasszázs – 80 perc', 'En behagelig helkroppsmassasje som bidrar til å løsne opp spenninger i muskulaturen, redusere hverdagens stress og fremme avslapning og velvære.', 'A relaxing full-body massage designed to ease muscle tension, reduce everyday stress and promote relaxation and well-being.', 'Kellemes, teljes testet átmozgató masszázs, amely segít ellazítani az izmokat, csökkenteni a mindennapi feszültséget és támogatja a testi-lelki kikapcsolódást.', 1575, '01:30:00', @cat, 1, 0), + ('massage', 'Klassisk svensk massasje', 'Classic Swedish Massage', 'Klasszikus svédmasszázs', 'Klassisk svensk massasje – 110 min', 'Classic Swedish Massage – 110 min', 'Klasszikus svédmasszázs – 110 perc', 'En behagelig helkroppsmassasje som bidrar til å løsne opp spenninger i muskulaturen, redusere hverdagens stress og fremme avslapning og velvære.', 'A relaxing full-body massage designed to ease muscle tension, reduce everyday stress and promote relaxation and well-being.', 'Kellemes, teljes testet átmozgató masszázs, amely segít ellazítani az izmokat, csökkenteni a mindennapi feszültséget és támogatja a testi-lelki kikapcsolódást.', 2050, '02:00:00', @cat, 1, 0), + ('massage', 'Sportsmassasje', 'Sports Massage', 'Sportmasszázs', 'Sportsmassasje – 50 min', 'Sports Massage – 50 min', 'Sportmasszázs – 50 perc', 'En målrettet massasje for aktive personer og idrettsutøvere. Behandlingen tilpasses individuelle behov og kan kombinere ulike teknikker som fascia- og triggerpunktteknikker, myofascial release, PNF, kopping, flossing eller taping.', 'A targeted massage for active individuals and athletes. The treatment is adapted to individual needs and may combine techniques such as fascia and trigger point techniques, myofascial release, PNF, cupping, flossing or taping.', 'Aktív életmódot folytatók és sportolók számára kialakított célzott masszázs. A kezelés az igényekhez igazodva különböző technikákat kombinálhat, például fascia- és triggerponttechnikákat, myofascial release-t, PNF-et, köpölyözést, flossingot vagy tapinget.', 1050, '01:00:00', @cat, 1, 0), + ('massage', 'Sportsmassasje', 'Sports Massage', 'Sportmasszázs', 'Sportsmassasje – 80 min', 'Sports Massage – 80 min', 'Sportmasszázs – 80 perc', 'En målrettet massasje for aktive personer og idrettsutøvere. Behandlingen tilpasses individuelle behov og kan kombinere ulike teknikker som fascia- og triggerpunktteknikker, myofascial release, PNF, kopping, flossing eller taping.', 'A targeted massage for active individuals and athletes. The treatment is adapted to individual needs and may combine techniques such as fascia and trigger point techniques, myofascial release, PNF, cupping, flossing or taping.', 'Aktív életmódot folytatók és sportolók számára kialakított célzott masszázs. A kezelés az igényekhez igazodva különböző technikákat kombinálhat, például fascia- és triggerponttechnikákat, myofascial release-t, PNF-et, köpölyözést, flossingot vagy tapinget.', 1575, '01:30:00', @cat, 1, 0), + ('massage', 'Deep Tissue-massasje', 'Deep Tissue Massage', 'Mélyszöveti masszázs', 'Deep Tissue-massasje – 50 min', 'Deep Tissue Massage – 50 min', 'Mélyszöveti masszázs – 50 perc', 'En mer intensiv massasje med fokus på dypere muskulatur og bindevev. Behandlingen har som mål å løsne vedvarende muskelspenninger, støtte bevegeligheten og arbeide målrettet med belastede områder.', 'A more intensive massage focusing on deeper muscles and connective tissue. It is designed to release persistent muscle tension, support mobility and target areas affected by physical strain.', 'Intenzívebb kezelés, amely a mélyebben fekvő izmokra és kötőszövetekre koncentrál. Célja a tartós izomfeszültség oldása, a mobilitás támogatása és a túlterhelt területek célzott kezelése.', 1050, '01:00:00', @cat, 1, 0), + ('massage', 'Deep Tissue-massasje', 'Deep Tissue Massage', 'Mélyszöveti masszázs', 'Deep Tissue-massasje – 80 min', 'Deep Tissue Massage – 80 min', 'Mélyszöveti masszázs – 80 perc', 'En mer intensiv massasje med fokus på dypere muskulatur og bindevev. Behandlingen har som mål å løsne vedvarende muskelspenninger, støtte bevegeligheten og arbeide målrettet med belastede områder.', 'A more intensive massage focusing on deeper muscles and connective tissue. It is designed to release persistent muscle tension, support mobility and target areas affected by physical strain.', 'Intenzívebb kezelés, amely a mélyebben fekvő izmokra és kötőszövetekre koncentrál. Célja a tartós izomfeszültség oldása, a mobilitás támogatása és a túlterhelt területek célzott kezelése.', 1575, '01:30:00', @cat, 1, 0), + ('massage', 'Arbeidsmassasje – kontor og fysisk arbeid', 'Work Recovery Massage – Office & Physical Work', 'Irodai és fizikai munka utáni masszázs', 'Arbeidsmassasje – kontor og fysisk arbeid – 50 min', 'Work Recovery Massage – Office & Physical Work – 50 min', 'Irodai és fizikai munka utáni masszázs – 50 perc', 'En massasje utviklet for spenninger som kan oppstå ved stillesittende, repetitivt eller fysisk krevende arbeid. Behandlingen fokuserer særlig på rygg, nakke, skuldre, armer og hender, og kan ved behov også inkludere hode og kjeveområdet.', 'A massage designed to relieve tension associated with sedentary, repetitive or physically demanding work. The treatment focuses primarily on the back, neck, shoulders, arms and hands, and may also include the head and jaw area when needed.', 'Ülőmunka, ismétlődő terhelés vagy fizikailag megterhelő munka után kialakuló feszültségek oldására összeállított kezelés. Elsősorban a hát, nyak, váll, kar és kéz területére koncentrál, szükség szerint a fej és az állkapocs környékének kezelésével kiegészítve.', 1050, '01:00:00', @cat, 1, 0), + ('massage', 'Arbeidsmassasje – kontor og fysisk arbeid', 'Work Recovery Massage – Office & Physical Work', 'Irodai és fizikai munka utáni masszázs', 'Arbeidsmassasje – kontor og fysisk arbeid – 80 min', 'Work Recovery Massage – Office & Physical Work – 80 min', 'Irodai és fizikai munka utáni masszázs – 80 perc', 'En massasje utviklet for spenninger som kan oppstå ved stillesittende, repetitivt eller fysisk krevende arbeid. Behandlingen fokuserer særlig på rygg, nakke, skuldre, armer og hender, og kan ved behov også inkludere hode og kjeveområdet.', 'A massage designed to relieve tension associated with sedentary, repetitive or physically demanding work. The treatment focuses primarily on the back, neck, shoulders, arms and hands, and may also include the head and jaw area when needed.', 'Ülőmunka, ismétlődő terhelés vagy fizikailag megterhelő munka után kialakuló feszültségek oldására összeállított kezelés. Elsősorban a hát, nyak, váll, kar és kéz területére koncentrál, szükség szerint a fej és az állkapocs környékének kezelésével kiegészítve.', 1575, '01:30:00', @cat, 1, 0), + ('massage', 'Studio Beve Signature – individuelt tilpasset massasje', 'Studio Beve Signature – Personalized Massage', 'Studio Beve Signature – személyre szabott masszázs', 'Studio Beve Signature – individuelt tilpasset massasje – 50 min', 'Studio Beve Signature – Personalized Massage – 50 min', 'Studio Beve Signature – személyre szabott masszázs – 50 perc', 'En individuelt tilpasset behandling for deg som ønsker en massasje basert på dine egne behov og mål. Etter en kort samtale velger og kombinerer massøren egnede massasje- og manuelle teknikker.', 'A fully personalized treatment tailored to your individual needs and goals. Following a brief consultation, the massage therapist selects and combines suitable massage and manual techniques.', 'Teljesen személyre szabott kezelés azok számára, akik egyéni igényeikhez és céljaikhoz igazított masszázst szeretnének. A masszőr a vendéggel történő rövid egyeztetés alapján választja ki és kombinálja a megfelelő masszázs- és manuális technikákat.', 1050, '01:00:00', @cat, 1, 0), + ('massage', 'Studio Beve Signature – individuelt tilpasset massasje', 'Studio Beve Signature – Personalized Massage', 'Studio Beve Signature – személyre szabott masszázs', 'Studio Beve Signature – individuelt tilpasset massasje – 80 min', 'Studio Beve Signature – Personalized Massage – 80 min', 'Studio Beve Signature – személyre szabott masszázs – 80 perc', 'En individuelt tilpasset behandling for deg som ønsker en massasje basert på dine egne behov og mål. Etter en kort samtale velger og kombinerer massøren egnede massasje- og manuelle teknikker.', 'A fully personalized treatment tailored to your individual needs and goals. Following a brief consultation, the massage therapist selects and combines suitable massage and manual techniques.', 'Teljesen személyre szabott kezelés azok számára, akik egyéni igényeikhez és céljaikhoz igazított masszázst szeretnének. A masszőr a vendéggel történő rövid egyeztetés alapján választja ki és kombinálja a megfelelő masszázs- és manuális technikákat.', 1575, '01:30:00', @cat, 1, 0), + ('massage', 'Studio Beve Signature – individuelt tilpasset massasje', 'Studio Beve Signature – Personalized Massage', 'Studio Beve Signature – személyre szabott masszázs', 'Studio Beve Signature – individuelt tilpasset massasje – 110 min', 'Studio Beve Signature – Personalized Massage – 110 min', 'Studio Beve Signature – személyre szabott masszázs – 110 perc', 'En individuelt tilpasset behandling for deg som ønsker en massasje basert på dine egne behov og mål. Etter en kort samtale velger og kombinerer massøren egnede massasje- og manuelle teknikker.', 'A fully personalized treatment tailored to your individual needs and goals. Following a brief consultation, the massage therapist selects and combines suitable massage and manual techniques.', 'Teljesen személyre szabott kezelés azok számára, akik egyéni igényeikhez és céljaikhoz igazított masszázst szeretnének. A masszőr a vendéggel történő rövid egyeztetés alapján választja ki és kombinálja a megfelelő masszázs- és manuális technikákat.', 2050, '02:00:00', @cat, 1, 0), + ('massage', 'Kraniosakral behandling', 'Craniosacral Treatment', 'Craniosacralis kezelés', 'Kraniosakral behandling – 50 min', 'Craniosacral Treatment – 50 min', 'Craniosacralis kezelés – 50 perc', 'En skånsom og helhetlig manuell behandling som med lette berøringer legger til rette for dyp avslapning og ro i kroppen. Behandlingen utføres med komfortable klær på.', 'A gentle, holistic manual treatment using light touch to promote deep relaxation and a sense of calm in the body. The treatment is performed while wearing comfortable clothing.', 'Gyengéd, holisztikus manuális kezelés, amely finom érintésekkel támogatja a mély relaxációt és a test ellazulását. A kezelés kényelmes ruházatban történik.', 1050, '01:00:00', @cat, 1, 0), + ('massage', 'Kraniosakral behandling', 'Craniosacral Treatment', 'Craniosacralis kezelés', 'Kraniosakral behandling – 80 min', 'Craniosacral Treatment – 80 min', 'Craniosacralis kezelés – 80 perc', 'En skånsom og helhetlig manuell behandling som med lette berøringer legger til rette for dyp avslapning og ro i kroppen. Behandlingen utføres med komfortable klær på.', 'A gentle, holistic manual treatment using light touch to promote deep relaxation and a sense of calm in the body. The treatment is performed while wearing comfortable clothing.', 'Gyengéd, holisztikus manuális kezelés, amely finom érintésekkel támogatja a mély relaxációt és a test ellazulását. A kezelés kényelmes ruházatban történik.', 1575, '01:30:00', @cat, 1, 0), + ('massage', 'Kraniosakral behandling', 'Craniosacral Treatment', 'Craniosacralis kezelés', 'Kraniosakral behandling – 110 min', 'Craniosacral Treatment – 110 min', 'Craniosacralis kezelés – 110 perc', 'En skånsom og helhetlig manuell behandling som med lette berøringer legger til rette for dyp avslapning og ro i kroppen. Behandlingen utføres med komfortable klær på.', 'A gentle, holistic manual treatment using light touch to promote deep relaxation and a sense of calm in the body. The treatment is performed while wearing comfortable clothing.', 'Gyengéd, holisztikus manuális kezelés, amely finom érintésekkel támogatja a mély relaxációt és a test ellazulását. A kezelés kényelmes ruházatban történik.', 2050, '02:00:00', @cat, 1, 0); + +-- ---------------------------------------------------------------- verify +SELECT service_id, service_category_no, service_name_no, service_price, + service_time AS booked_slot, is_enabled + FROM services WHERE service_category_id = @cat ORDER BY service_id; + +SELECT worker_id, worker_name, worker_profile_img, service_category_id + FROM workers WHERE worker_id = @worker; diff --git a/documents/massage_vertical_migration.sql b/documents/massage_vertical_migration.sql new file mode 100644 index 0000000..516ccef --- /dev/null +++ b/documents/massage_vertical_migration.sql @@ -0,0 +1,123 @@ +-- ============================================================================ +-- Massage vertical - data migration +-- ============================================================================ +-- Studio Beve, 2026-08-10. +-- +-- NOTE: there is NO DDL here. The massage vertical needed no schema change: +-- the vertical is derived from workers.service_category_id, and +-- services.service_type is already a VARCHAR. No ALTER, no backfill, +-- no UPDATE of any existing row. +-- +-- ORDER IS LOAD-BEARING. Run the whole file in ONE session (it uses @vars): +-- +-- 1. service_categories FIRST. getAllServiceByServiceType() and +-- getWorkersByCategorySlug() both INNER JOIN service_categories, so a +-- dangling category_id makes rows silently INVISIBLE rather than erroring. +-- 2. The worker row, carrying that category id. Must exist before any +-- massage service is enabled, or the public booking page renders an +-- empty step 2. +-- 3. worker_schedule rows for every bookable weekday. NOT OPTIONAL, and +-- worker_schedule_overrides alone are NOT sufficient - getAvailableTimes() +-- returns [] without a schedule row, and booking_process() rejects with +-- 'worker_unavailable'. +-- 4. Services inserted with is_enabled = '0'. Flipping that to '1' is the +-- go-live switch (step 9), after the smoke test. +-- +-- DEMO DATA: the therapist ("Nora Lindqvist"), the four services and their +-- prices are placeholders. Replace with real values before production. +-- ============================================================================ + +SET NAMES utf8mb4; + +-- ---------------------------------------------------------------- 1. category +INSERT INTO service_categories (serv_cat_slug, serv_cat_name, is_deleted) +VALUES ('massage', 'Masszázs', 0); + +SET @cat := LAST_INSERT_ID(); +SELECT @cat AS new_service_category_id; + +-- ---------------------------------------------------------------- 2. worker +-- is_beauty / is_barber are the legacy columns; nothing reads them any more +-- (the vertical is derived from service_category_id). They are NOT NULL, so +-- they are written as 0 here to stay consistent with the derived value. +INSERT INTO workers ( + worker_name, worker_profile_img, worker_info, + service_category_id, is_barber, is_beauty, is_active, is_deleted, + google_calendar_id, ntfy_topic, + lunch_window_start, lunch_window_end, lunch_preferred_time +) VALUES ( + 'Nora Lindqvist', 'worker_Nora_Lindqvist.jpg', '', + @cat, 0, 0, 1, 0, + NULL, NULL, + '12:00:00', '14:00:00', '12:30:00' +); + +SET @worker := LAST_INSERT_ID(); +SELECT @worker AS new_worker_id; + +-- ---------------------------------------------------------------- 3. schedule +-- weekday follows PHP date('w'): 0 = Sunday .. 6 = Saturday. +-- Mon-Fri 10:00-18:00, Sat 10:00-16:00, Sun closed (no row). +INSERT INTO worker_schedule (worker_id, weekday, start_time, end_time, is_alternate_week) VALUES + (@worker, 1, '10:00:00', '18:00:00', 0), + (@worker, 2, '10:00:00', '18:00:00', 0), + (@worker, 3, '10:00:00', '18:00:00', 0), + (@worker, 4, '10:00:00', '18:00:00', 0), + (@worker, 5, '10:00:00', '18:00:00', 0), + (@worker, 6, '10:00:00', '16:00:00', 0); + +-- ---------------------------------------------------------------- 4. services +-- Inserted DISABLED. service_time drives the slot length, so it also decides +-- how many slots the therapist appears to have against the 18:00 close: +-- a 90-minute service has its last possible start at 16:30. +INSERT INTO services ( + service_type, + service_category_no, service_category_en, service_category_hu, + service_name_no, service_name_en, service_name_hu, + service_description_no, service_description_en, service_description_hu, + service_price, service_time, service_category_id, is_enabled, is_deleted +) VALUES + ('massage', + 'Massasje', 'Massage', 'Masszázs', + 'Klassisk massasje 60 min', 'Classic massage 60 min', 'Klasszikus masszázs 60 perc', + 'Helkropp, avslappende', 'Full body, relaxing', 'Teljes test, lazító', + 900, '01:00:00', @cat, 0, 0), + + ('massage', + 'Massasje', 'Massage', 'Masszázs', + 'Klassisk massasje 90 min', 'Classic massage 90 min', 'Klasszikus masszázs 90 perc', + 'Helkropp, utvidet behandling', 'Full body, extended treatment', 'Teljes test, hosszabb kezelés', + 1300, '01:30:00', @cat, 0, 0), + + ('massage', + 'Massasje', 'Massage', 'Masszázs', + 'Dypvevsmassasje 60 min', 'Deep tissue massage 60 min', 'Mélyszöveti masszázs 60 perc', + 'Mot spenninger i nakke, skuldre og korsrygg', 'For tension in neck, shoulders and lower back', 'Nyaki, vállövi és deréktáji feszülésre', + 1000, '01:00:00', @cat, 0, 0), + + ('massage', + 'Massasje', 'Massage', 'Masszázs', + 'Nakke og skuldre 30 min', 'Neck and shoulders 30 min', 'Nyak és váll 30 perc', + 'Kort behandling for stillesittende arbeid', 'Short treatment for desk-bound days', 'Rövid kezelés ülőmunkához', + 550, '00:30:00', @cat, 0, 0); + +-- ---------------------------------------------------------------- verification +SELECT 'category' AS step, service_category_id AS id, serv_cat_slug, serv_cat_name + FROM service_categories WHERE service_category_id = @cat; + +SELECT 'worker' AS step, worker_id, worker_name, service_category_id, is_active + FROM workers WHERE worker_id = @worker; + +SELECT 'schedule' AS step, weekday, start_time, end_time + FROM worker_schedule WHERE worker_id = @worker ORDER BY weekday; + +SELECT 'services' AS step, service_id, service_name_no, service_price, service_time, is_enabled + FROM services WHERE service_category_id = @cat ORDER BY service_id; + +-- The derived-vertical check: this is what getActiveWorkers('massage') resolves. +SELECT 'derived' AS step, w.worker_id, w.worker_name, + (SELECT GROUP_CONCAT(DISTINCT s.service_type ORDER BY s.service_type) + FROM services s + WHERE s.service_category_id = w.service_category_id + AND s.is_deleted != '1') AS derived_verticals + FROM workers w WHERE w.worker_id = @worker; diff --git a/sitemap.xml b/sitemap.xml index 6a47da0..d72de91 100755 --- a/sitemap.xml +++ b/sitemap.xml @@ -84,4 +84,39 @@ + + https://studiobeve.no/no/massage + 2026-08-10T00:00:00+00:00 + 0.80 + + + https://studiobeve.no/no/massage/ + 2026-08-10T00:00:00+00:00 + 0.80 + + + https://studiobeve.no/en/massage/ + 2026-08-10T00:00:00+00:00 + 0.80 + + + https://studiobeve.no/hu/massage/ + 2026-08-10T00:00:00+00:00 + 0.80 + + + https://studiobeve.no/no/booking/massage + 2026-08-10T00:00:00+00:00 + 0.64 + + + https://studiobeve.no/en/booking/massage + 2026-08-10T00:00:00+00:00 + 0.64 + + + https://studiobeve.no/hu/booking/massage + 2026-08-10T00:00:00+00:00 + 0.64 + \ No newline at end of file