.hbw-widget {
--hbw-warm-white: #F8F5F0;
--hbw-bone: #EFE9DF;
--hbw-sand: #E0D4BF;
--hbw-clay: #C9A98C;
--hbw-olive: #6B6850;
--hbw-charcoal: #2B2A26;
--hbw-ink: #1A1917;
font-family: 'Jost', -apple-system, BlinkMacSystemFont, sans-serif;
color: var(--hbw-charcoal);
background: var(--hbw-warm-white);
padding: 12px 14px;
border-radius: 3px;
box-shadow:
0 1px 2px rgba(43, 42, 38, 0.04),
0 16px 48px rgba(43, 42, 38, 0.10);
max-width: 1240px;
margin: 0 auto;
box-sizing: border-box;
line-height: 1.4;
}
.hbw-widget *,
.hbw-widget *::before,
.hbw-widget *::after {
box-sizing: border-box;
}
.hbw-row {
display: flex;
align-items: stretch;
gap: 0;
}
/* ================================================================
FIELDS
================================================================ */
.hbw-field {
position: relative;
flex: 1 1 0;
padding: 10px 18px;
border-right: 1px solid var(--hbw-sand);
min-width: 0;
}
.hbw-field:first-child {
padding-left: 14px;
}
.hbw-field--property {
flex: 1.15 1 0;
}
.hbw-field--guests {
cursor: pointer;
}
.hbw-field--promo {
flex: 1 1 0;
}
.hbw-field--last {
border-right: none;
}
/* ================================================================
LABELS
================================================================ */
.hbw-label {
display: block;
font-family: 'Jost', sans-serif;
font-size: 0.66rem;
font-weight: 400;
letter-spacing: 0.18em;
text-transform: uppercase;
color: var(--hbw-olive);
margin: 0 0 6px;
pointer-events: none;
}
.hbw-value {
display: block;
font-family: 'Cormorant Garamond', Georgia, serif;
font-size: 1.3rem;
font-weight: 400;
color: var(--hbw-ink);
letter-spacing: 0.005em;
line-height: 1.2;
white-space: nowrap;
overflow: hidden;
text-overflow: ellipsis;
pointer-events: none;
}
/* ================================================================
CHEVRON
================================================================ */
.hbw-chevron {
position: absolute;
right: 14px;
top: 50%;
width: 8px;
height: 8px;
border-right: 1px solid var(--hbw-olive);
border-bottom: 1px solid var(--hbw-olive);
transform:
translateY(-75%)
rotate(45deg);
pointer-events: none;
transition:
transform 200ms ease-out;
}
.hbw-field.hbw-is-open .hbw-chevron {
transform:
translateY(-25%)
rotate(-135deg);
}
/* ================================================================
DATE INPUTS
================================================================ */
.hbw-date-input {
position: absolute;
inset: 0;
opacity: 0;
cursor: pointer;
border: 0;
padding: 0;
margin: 0;
background: transparent;
font-family: inherit;
font-size: 16px;
color: transparent;
width: 100%;
height: 100%;
}
.hbw-date-input::-webkit-calendar-picker-indicator {
opacity: 0;
cursor: pointer;
}
/* ================================================================
PROMO CODE
================================================================ */
.hbw-promo-wrap {
position: relative;
display: flex;
align-items: center;
}
.hbw-promo-input {
display: block;
width: 100%;
border: 0;
background: transparent;
font-family:
'Cormorant Garamond',
Georgia,
serif;
font-size: 1.3rem;
color: var(--hbw-ink);
padding: 0 32px 0 0;
margin: 0;
outline: none;
letter-spacing: 0.005em;
line-height: 1.2;
}
.hbw-promo-input::placeholder {
color: var(--hbw-clay);
font-style: italic;
opacity: 1;
}
.hbw-promo-clear {
position: absolute;
right: 0;
top: 50%;
transform: translateY(-50%);
width: 28px;
height: 28px;
border: 0;
padding: 0;
background: transparent;
color: var(--hbw-olive);
font-family: 'Jost', sans-serif;
font-size: 1.15rem;
font-weight: 300;
line-height: 1;
cursor: pointer;
display: none;
align-items: center;
justify-content: center;
}
.hbw-promo-clear.hbw-is-visible {
display: flex;
}
.hbw-promo-clear:hover {
color: var(--hbw-ink);
}
/* ================================================================
POPOVER
================================================================ */
.hbw-popover {
position: absolute;
top: calc(100% + 14px);
left: -1px;
right: -1px;
background: var(--hbw-bone);
border:
1px solid
var(--hbw-sand);
border-radius: 3px;
padding: 6px 22px;
box-shadow:
0 12px 32px
rgba(43, 42, 38, 0.14);
z-index: 100;
display: none;
cursor: default;
min-width: 240px;
}
.hbw-popover.hbw-is-open {
display: block;
}
.hbw-pop-row {
display: flex;
align-items: center;
justify-content:
space-between;
padding: 14px 0;
}
.hbw-pop-row +
.hbw-pop-row {
border-top:
1px solid
var(--hbw-sand);
}
.hbw-pop-label {
font-family:
'Cormorant Garamond',
Georgia,
serif;
font-size: 1.15rem;
color: var(--hbw-ink);
line-height: 1;
}
.hbw-pop-sub-inline {
display: block;
font-family:
'Jost',
sans-serif;
font-size: 0.66rem;
font-weight: 400;
letter-spacing: 0.15em;
text-transform:
uppercase;
color: var(--hbw-olive);
margin-top: 3px;
}
/* ================================================================
STEPPERS
================================================================ */
.hbw-stepper {
display: flex;
align-items: center;
gap: 16px;
}
.hbw-step-btn {
width: 30px;
height: 30px;
border-radius: 50%;
border:
1px solid
var(--hbw-charcoal);
background:
transparent;
color:
var(--hbw-charcoal);
font-family:
'Jost',
sans-serif;
font-size: 1rem;
font-weight: 300;
cursor: pointer;
transition:
background-color 200ms ease-out,
color 200ms ease-out;
display: flex;
align-items: center;
justify-content: center;
padding: 0;
line-height: 1;
}
.hbw-step-btn:hover:not(:disabled) {
background:
var(--hbw-charcoal);
color:
var(--hbw-warm-white);
}
.hbw-step-btn:disabled {
opacity: 0.3;
cursor: not-allowed;
}
.hbw-step-count {
font-family:
'Cormorant Garamond',
Georgia,
serif;
font-size: 1.2rem;
color:
var(--hbw-ink);
min-width: 18px;
text-align: center;
}
/* ================================================================
RESERVE BUTTON
================================================================ */
.hbw-cta {
flex-shrink: 0;
background:
var(--hbw-charcoal);
color:
var(--hbw-warm-white);
border: 0;
padding:
0 36px;
margin:
6px 6px
6px 12px;
font-family:
'Jost',
sans-serif;
font-size: 0.74rem;
font-weight: 400;
letter-spacing:
0.22em;
text-transform:
uppercase;
cursor: pointer;
border-radius: 2px;
transition:
background-color
240ms ease-out;
min-width: 170px;
white-space: nowrap;
}
.hbw-cta:hover {
background:
var(--hbw-ink);
}
.hbw-cta:focus-visible {
outline:
2px solid
var(--hbw-ink);
outline-offset: 2px;
}
.hbw-field--guests
.hbw-value {
padding-right: 18px;
}
/* ================================================================
MOBILE
================================================================ */
@media (max-width: 1024px) {
.hbw-widget {
padding: 8px;
}
.hbw-row {
flex-direction: column;
}
.hbw-chevron {
display: none;
}
.hbw-field {
border-right: 0;
border-bottom:
1px solid
var(--hbw-sand);
padding:
16px 18px;
flex: 1 1 auto;
}
.hbw-field:first-child {
padding-left: 18px;
}
.hbw-field--last {
border-bottom: 0;
}
.hbw-popover {
position: static;
box-shadow: none;
border: 0;
border-top:
1px solid
var(--hbw-sand);
margin:
14px -18px -16px;
padding:
0 18px;
border-radius: 0;
min-width: 0;
}
.hbw-cta {
margin:
14px 6px 6px;
padding: 18px;
width:
calc(100% - 12px);
}
}
(function () {
/* ================================================================
CONFIGURATION
================================================================ */
var CONFIG = {
baseUrl:
'https://us2.cloudbeds.com/reservation/Dp2HwT',
paramNames: {
checkin:
'checkin',
checkout:
'checkout',
guests:
'guests',
promo:
'promo'
},
extraParams: {
currency:
'eur'
},
minAdults: 1,
maxAdults: 8,
maxChildren: 6,
/*
* Arrival automatically starts tomorrow.
*/
defaultCheckinOffsetDays: 1,
/*
* Default stay:
* 3 nights.
*/
defaultStayLengthDays: 3,
/*
* Default Villa Melissi rate code.
*
* Guest can remove it with X.
*/
defaultPromo:
'Melissi-Rate',
openInNewTab:
true,
displayLocale:
'en-GB'
};
/* ================================================================
ELEMENTS
================================================================ */
var root =
document.getElementById(
'hbw-widget'
);
if (!root) {
return;
}
var checkinInput =
root.querySelector(
'#hbw-checkin'
);
var checkoutInput =
root.querySelector(
'#hbw-checkout'
);
var checkinDisp =
root.querySelector(
'#hbw-checkin-display'
);
var checkoutDisp =
root.querySelector(
'#hbw-checkout-display'
);
var guestsField =
root.querySelector(
'#hbw-guests-field'
);
var guestsPop =
root.querySelector(
'#hbw-guests-popover'
);
var guestsDisp =
root.querySelector(
'#hbw-guests-display'
);
var adultsCount =
root.querySelector(
'#hbw-adults-count'
);
var childrenCount =
root.querySelector(
'#hbw-children-count'
);
var promoInput =
root.querySelector(
'#hbw-promo'
);
var promoClear =
root.querySelector(
'#hbw-promo-clear'
);
var cta =
root.querySelector(
'#hbw-cta'
);
/* ================================================================
STATE
================================================================ */
var state = {
checkin:
null,
checkout:
null,
adults:
2,
children:
0,
promo:
CONFIG.defaultPromo
};
/* ================================================================
DATE HELPERS
================================================================ */
function addDays(
date,
days
) {
var d =
new Date(
date.getTime()
);
d.setDate(
d.getDate() + days
);
return d;
}
function toISO(date) {
return (
date.getFullYear()
+ '-'
+ String(
date.getMonth() + 1
).padStart(
2,
'0'
)
+ '-'
+ String(
date.getDate()
).padStart(
2,
'0'
)
);
}
function parseISO(str) {
if (!str) {
return null;
}
var parts =
str.split('-');
if (
parts.length !== 3
) {
return null;
}
return new Date(
Number(
parts[0]
),
Number(
parts[1]
) - 1,
Number(
parts[2]
)
);
}
function formatDisplay(date) {
return date.toLocaleDateString(
CONFIG.displayLocale,
{
weekday:
'short',
day:
'numeric',
month:
'short'
}
);
}
/* ================================================================
DEFAULT DATES
================================================================ */
var today =
new Date();
today.setHours(
0,
0,
0,
0
);
/*
* Tomorrow is both:
*
* 1. The default arrival
* 2. The earliest selectable arrival
*/
var tomorrow =
addDays(
today,
CONFIG.defaultCheckinOffsetDays
);
state.checkin =
tomorrow;
state.checkout =
addDays(
state.checkin,
CONFIG.defaultStayLengthDays
);
/*
* Arrival cannot be today or earlier.
*/
checkinInput.min =
toISO(
tomorrow
);
checkinInput.value =
toISO(
state.checkin
);
/*
* Departure must be at least
* one day after arrival.
*/
checkoutInput.min =
toISO(
addDays(
state.checkin,
1
)
);
checkoutInput.value =
toISO(
state.checkout
);
checkinDisp.textContent =
formatDisplay(
state.checkin
);
checkoutDisp.textContent =
formatDisplay(
state.checkout
);
/* ================================================================
DATE PICKER
================================================================ */
function openPicker(input) {
try {
if (
typeof input.showPicker ===
'function'
) {
input.showPicker();
} else {
input.focus();
}
} catch (err) {
input.focus();
}
}
checkinInput.addEventListener(
'click',
function () {
openPicker(
checkinInput
);
}
);
checkoutInput.addEventListener(
'click',
function () {
openPicker(
checkoutInput
);
}
);
/* ================================================================
ARRIVAL CHANGE
================================================================ */
checkinInput.addEventListener(
'change',
function () {
var selected =
parseISO(
checkinInput.value
);
if (!selected) {
return;
}
/*
* Prevent today/past dates,
* even if browser validation
* is bypassed.
*/
if (
selected < tomorrow
) {
selected =
tomorrow;
checkinInput.value =
toISO(
tomorrow
);
}
state.checkin =
selected;
checkinDisp.textContent =
formatDisplay(
selected
);
var minimumCheckout =
addDays(
selected,
1
);
checkoutInput.min =
toISO(
minimumCheckout
);
/*
* If existing checkout becomes
* invalid after changing arrival,
* automatically move checkout.
*/
if (
state.checkout <=
selected
) {
state.checkout =
minimumCheckout;
checkoutInput.value =
toISO(
minimumCheckout
);
checkoutDisp.textContent =
formatDisplay(
minimumCheckout
);
}
}
);
/* ================================================================
DEPARTURE CHANGE
================================================================ */
checkoutInput.addEventListener(
'change',
function () {
var selected =
parseISO(
checkoutInput.value
);
if (!selected) {
return;
}
/*
* Departure must be later
* than arrival.
*/
if (
selected 0
) {
text +=
' · '
+ state.children
+ ' '
+ (
state.children === 1
? 'Baby'
: 'Babies'
);
}
guestsDisp.textContent =
text;
adultsCount.textContent =
state.adults;
childrenCount.textContent =
state.children;
root
.querySelectorAll(
'.hbw-step-btn'
)
.forEach(
function (button) {
var type =
button.dataset.type;
var operation =
button.dataset.op;
var disabled =
false;
if (
type === 'adults'
&&
operation === 'inc'
) {
disabled =
state.adults >=
CONFIG.maxAdults;
}
if (
type === 'adults'
&&
operation === 'dec'
) {
disabled =
state.adults =
CONFIG.maxChildren;
}
if (
type === 'children'
&&
operation === 'dec'
) {
disabled =
state.children <= 0;
}
button.disabled =
disabled;
}
);
}
/* ================================================================
GUEST STEPPERS
================================================================ */
root
.querySelectorAll(
'.hbw-step-btn'
)
.forEach(
function (button) {
button.addEventListener(
'click',
function (event) {
event.stopPropagation();
var type =
button.dataset.type;
var operation =
button.dataset.op;
/*
* Guests
*/
if (
type === 'adults'
) {
if (
operation === 'inc'
&&
state.adults
CONFIG.minAdults
) {
state.adults--;
}
}
/*
* Babies
*/
if (
type === 'children'
) {
if (
operation === 'inc'
&&
state.children 0
) {
state.children--;
}
}
updateGuestsDisplay();
}
);
}
);
updateGuestsDisplay();
/* ================================================================
CLOSE POPOVER
================================================================ */
document.addEventListener(
'click',
function (event) {
if (
!guestsField.contains(
event.target
)
) {
guestsPop.classList.remove(
'hbw-is-open'
);
guestsField.classList.remove(
'hbw-is-open'
);
}
}
);
document.addEventListener(
'keydown',
function (event) {
if (
event.key ===
'Escape'
) {
guestsPop.classList.remove(
'hbw-is-open'
);
guestsField.classList.remove(
'hbw-is-open'
);
}
}
);
/* ================================================================
PROMO CODE
================================================================ */
function updatePromoState() {
state.promo =
promoInput.value.trim();
/*
* Show remove X only
* when a code exists.
*/
promoClear.classList.toggle(
'hbw-is-visible',
state.promo.length > 0
);
}
/*
* Initialise default
* Melissi-Rate promo.
*/
promoInput.value =
CONFIG.defaultPromo;
updatePromoState();
/*
* Update promo if guest manually
* edits the field.
*/
promoInput.addEventListener(
'input',
function () {
updatePromoState();
}
);
promoInput.addEventListener(
'click',
function (event) {
event.stopPropagation();
}
);
/*
* X removes promo completely.
*/
promoClear.addEventListener(
'click',
function (event) {
event.preventDefault();
event.stopPropagation();
promoInput.value =
'';
state.promo =
'';
updatePromoState();
promoInput.focus();
}
);
/* ================================================================
BOOKING REDIRECT
================================================================ */
cta.addEventListener(
'click',
function () {
/*
* Start from clean
* Villa Melissi Cloudbeds URL.
*/
var bookingUrl =
new URL(
CONFIG.baseUrl
);
/*
* Extra parameters.
*/
Object
.keys(
CONFIG.extraParams
)
.forEach(
function (key) {
bookingUrl
.searchParams
.set(
key,
CONFIG
.extraParams[
key
]
);
}
);
/*
* Arrival.
*/
bookingUrl
.searchParams
.set(
CONFIG
.paramNames
.checkin,
toISO(
state.checkin
)
);
/*
* Departure.
*/
bookingUrl
.searchParams
.set(
CONFIG
.paramNames
.checkout,
toISO(
state.checkout
)
);
/*
* Total guests.
*
* Adults + babies.
*/
bookingUrl
.searchParams
.set(
CONFIG
.paramNames
.guests,
String(
state.adults
+
state.children
)
);
/*
* PROMO CODE
*
* If Melissi-Rate or another
* code exists:
*
* ?promo=Melissi-Rate
*
* If guest presses X:
* promo parameter is deleted
* completely.
*/
if (
state.promo
) {
bookingUrl
.searchParams
.set(
CONFIG
.paramNames
.promo,
state.promo
);
} else {
bookingUrl
.searchParams
.delete(
CONFIG
.paramNames
.promo
);
}
/*
* Open Cloudbeds.
*/
if (
CONFIG.openInNewTab
) {
window.open(
bookingUrl.toString(),
'_blank',
'noopener,noreferrer'
);
} else {
window.location.href =
bookingUrl.toString();
}
}
);
})();