/* ============================================================================
   Páter Apartmanok Siófok — v2.1
   Written from scratch. Nothing is inherited from the v2.0 stylesheet.

   Direction: a light, airy, premium lakeside hotel. Photography carries the
   visual weight; colour never does. There is no dark theme and no dark
   section anywhere — the only darkening in the whole sheet is a small,
   local gradient at the foot of a hero photo, so a white headline stays
   legible over an unpredictable image.

   Palette: sage-led, on warm sand grounds. The pastel itself (--sage) is
   decorative only — it sits around 2:1 against the page and may never carry
   text or a meaningful icon. Anything that must be read uses the deep end of
   the same ramp (--sage-deep for fills, --sage-ink for text). This is the one
   rule that keeps a pastel brand accessible.
   ========================================================================= */

/* --------------------------------------------------------------- @font-face
   Self-hosted, latin + latin-ext so Hungarian (ő ű) and Polish (ł ż ą ę)
   render in the real face rather than a fallback. Swapping the pairing later
   means replacing these four files and the two --font-* tokens; nothing else
   in the sheet names a typeface. */
@font-face {
  font-family: 'Madefor Display';
  src: url('/assets/fonts/madefor-display-latin.woff2') format('woff2');
  font-weight: 400 800; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215;
}
@font-face {
  font-family: 'Madefor Display';
  src: url('/assets/fonts/madefor-display-latin-ext.woff2') format('woff2');
  font-weight: 400 800; font-style: normal; font-display: swap;
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Madefor Text';
  src: url('/assets/fonts/madefor-text-latin.woff2') format('woff2');
  font-weight: 300 700; font-style: normal; font-display: swap;
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA,
    U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215;
}
@font-face {
  font-family: 'Madefor Text';
  src: url('/assets/fonts/madefor-text-latin-ext.woff2') format('woff2');
  font-weight: 300 700; font-style: normal; font-display: swap;
  unicode-range: U+0100-024F, U+0259, U+1E00-1EFF, U+2020, U+20A0-20AB,
    U+20AD-20CF, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

/* ------------------------------------------------------------------- tokens */
:root {
  /* — colour ————————————————————————————————————————————————
     Every value below is measured, not guessed. The contrast figures in the
     comments are WCAG ratios against the surface named. */
  --page:      #FFFEFC;   /* off-white, a hair warm so photos sit in it     */
  --surface:   #FFFFFF;   /* a card lifted off a tinted band                */

  /* The warm sand GROUNDS: tints a page sits ON, not the accent. They stayed
     put when the accent went green (§44) — they ARE the warm half of the
     secondary palette, and the sage was chosen to sit with them. */
  --sand-1:    #FBF2E6;   /* card / panel tint            1.10:1 vs page    */
  --sand-2:    #F7E8D5;   /* section band tint            1.19:1 vs page    */
  --sand-3:    #EBD6B9;   /* rules and dividers                             */

  /* — PRIMARY ACCENT ————————————————————————————————————————
     Garden sage, H122 S30%. Deliberately desaturated, and the reason is on
     the page already: the WhatsApp FAB is #0F7A3D (H146 S78%), a green that
     appears on every single view. An accent differing from it only in
     lightness would read as a mistake rather than a decision. This sits 24°
     away in hue AND at well under half its saturation. */
  --sage:        #B7D3B3; /* PASTEL ACCENT — decorative only, never text    */
  --sage-deep:   #3E7340; /* button fill        white on it       5.63:1 AA */
  --sage-deeper: #305E32; /* primary button, hovered              7.57:1 AA */
  --sage-ink:    #2C612D; /* link + accent text on page           7.30:1 AAA*/

  /* supporting pastels — tints and dividers only, never text, never fills.
     sage-1/-3 were already H120 and the accent landed at H122, so these two
     needed no adjustment at all to stay in family with it. */
  --sage-1:    #ECF7EC;
  --sage-3:    #C7E2C7;
  --blue-1:    #E9F6FE;
  --blue-3:    #BEDEF3;

  /* — ink ————————————————————————————————————————————————————
     Warm neutral. --ink-3 is deliberately dark enough to clear 4.5:1 on the
     sand band as well as on the page, which is where a lighter grey fails. */
  --ink:       #211D18;   /* headings          16.62:1 page                 */
  --ink-2:     #4C4741;   /* body               9.12:1 page                 */
  --ink-3:     #6E6860;   /* meta, captions     5.47:1 page / 4.58:1 band   */
  --ink-4:     #979189;   /* decorative marks only — never text             */
  --line:      #E2DDD7;   /* hairline                                       */
  --line-2:    #D3CDC4;   /* hairline, emphasised                           */

  /* FOREGROUND tokens. Separate concern from the background tints above:
     a ground may be as pale as it likes, but anything drawn ON it has to be
     seen. These two exist because the tints could not be reused as strokes.
       --line-strong  every UI component boundary (WCAG 1.4.11 wants 3:1):
                      form fields, outline buttons, filter chips, empty stars
       --sage-rule    the affordance rule under a link
     Both are measured against the WORST ground in the design (--sand-2), not
     against the page, because that is where a stroke actually disappears. */
  --line-strong: #8D857B;  /* 3.61:1 page · 3.02:1 sand band                */
  --sage-rule:   #59915B;  /* 3.63:1 page · 3.10:1 sand band                */

  --white:     #FFFFFF;
  --focus:     #1B5D7F;   /* focus ring — blue so it never reads as brand   */

  /* — type ———————————————————————————————————————————————————
     Two tokens name the faces. Replacing the pairing = replace the woff2
     files above and these two lines. */
  --font-display: 'Madefor Display', ui-sans-serif, system-ui, sans-serif;
  --font-text:    'Madefor Text', ui-sans-serif, system-ui, sans-serif;

  /* Editorial scale — deliberately small. v2.0's headings ran to ~4rem and
     that oversizing is most of what made it read as a template. */
  --step--2: 0.75rem;                              /* 12px  fine print      */
  --step--1: 0.8125rem;                            /* 13px  meta            */
  --step-0:  1rem;                                 /* 16px  body            */
  --step-1:  1.0625rem;                            /* 17px  lead body       */
  --step-2:  clamp(1.1875rem, 0.6vw + 1.05rem, 1.375rem);   /* h3           */
  --step-3:  clamp(1.4375rem, 1.4vw + 1.1rem, 1.875rem);    /* h2           */
  --step-4:  clamp(1.75rem,   2.2vw + 1.2rem,  2.5rem);     /* page h1      */
  --step-5:  clamp(2rem,      3.2vw + 1.2rem,  3rem);       /* hero h1      */

  --lh-tight: 1.14;
  --lh-head:  1.22;
  --lh-body:  1.65;
  --ls-tight: -0.021em;
  --ls-head:  -0.014em;

  /* — space ——————————————————————————————————————————————————
     A slower rhythm than v2.0. Sections breathe; nothing is crammed. */
  --sp-1: 0.25rem;  --sp-2: 0.5rem;   --sp-3: 0.75rem;  --sp-4: 1rem;
  --sp-5: 1.5rem;   --sp-6: 2rem;     --sp-7: 3rem;     --sp-8: 4rem;
  --sp-9: 6rem;
  --section-y: clamp(4.5rem, 8vw, 8rem);
  --gutter:    clamp(1.25rem, 4vw, 2.5rem);
  --measure:   68ch;

  /* — form ————————————————————————————————————————————————————
     ONE height for every control that can sit in a row with another one.
     Padding alone did not deliver that: an <input type="date"> lays out its
     internal date-editing fields taller than a <select> lays out its option
     text, so with identical padding and font-size they measured 52.8 and 50.8,
     and the hero's booking row rendered with a visibly short guest field and a
     shorter button still (46.5). Nothing about that was in the CSS to read —
     it came from the controls' intrinsic content boxes. Fixing the height
     removes the question. */
  --control-h: 3.3rem;    /* 52.8px — the tallest intrinsic, so nothing clips */
  /* ── corners ───────────────────────────────────────────────────────
     ONE radius for the entire site, and it is none.

     v2.1 used to run four at once — 6px chrome, 10px inputs, 18px cards and
     photographs, 999px pills — which was survivable only while everything
     was soft. The moment the buttons went sharp the page was speaking two
     languages, and a partial fix (sharp controls, soft pictures) was tried
     and rejected: at the sizes these photographs actually render, a 10px
     corner does not read as warmth, it reads as an unfinished edit.

     So: every corner in the system is square. The warmth on this site comes
     from the sand palette, the photography and the pace of the layout, not
     from rounded rectangles, and hard edges suit an editorial page that is
     mostly type and pictures.

     THE ONE EXCEPTION, and it is a shape rather than a radius: things that
     are genuinely CIRCLES stay circles — the floating WhatsApp and
     scroll-to-top buttons, the lightbox arrows, the hero dots, the gallery
     zoom disc, icon discs and avatars. A circle is what those objects ARE,
     not a corner treatment applied to a rectangle, and squaring the WhatsApp
     mark would cost the recognisability §22 deliberately bought.

     The rule, in one line: every corner is square; circles stay circles. */
  --radius: 0;

  /* Shadows are rationed. Only genuinely floating things get one: the search
     card, the lightbox, the drawer, the sticky bar, the cookie note. A card
     that merely sits on the page gets a hairline instead. */
  --lift-1: 0 1px 2px rgba(33, 29, 24, 0.04), 0 6px 16px -8px rgba(33, 29, 24, 0.10);
  --lift-2: 0 2px 6px rgba(33, 29, 24, 0.06), 0 18px 40px -18px rgba(33, 29, 24, 0.18);
  --lift-3: 0 8px 24px rgba(33, 29, 24, 0.10), 0 40px 80px -30px rgba(33, 29, 24, 0.28);

  /* ------------------------------------------------------------- motion
     Four curves and five durations. Everything that moves on this site picks
     from these, so the whole thing reads as one system rather than a pile of
     individually-tuned effects. Nothing invents its own numbers.

     The built-in CSS easings are too weak to feel deliberate, and `ease-in`
     never appears: it starts slowly, which is precisely the moment the user is
     watching, so the same duration *feels* slower.

     Only transform and opacity are ever animated, so every one of these runs
     on the compositor and none of it touches layout while scrolling. */
  --ease-out:    cubic-bezier(0.23, 1, 0.32, 1);    /* entering / leaving */
  --ease-in-out: cubic-bezier(0.77, 0, 0.175, 1);   /* moving on screen */
  --ease:        cubic-bezier(0.4, 0, 0.2, 1);      /* hover, colour */
  --ease-drawer: cubic-bezier(0.32, 0.72, 0, 1);    /* the drawer */

  --t-press:  120ms;   /* press feedback: must feel instant */
  --t-hover:  180ms;   /* hover response */
  --t-pop:    220ms;   /* small things arriving: chips, lightbox chrome */
  --t-panel:  300ms;   /* larger surfaces: drawer, lightbox, accordion */
  --t-reveal: 520ms;   /* scroll reveals — decorative, may breathe */
  /* The border trace. It is still the one motion here meant to be WATCHED
     rather than merely noticed — but it is hover feedback first, and hover
     feedback that is still resolving a second later reads as lag, not as
     craft. 1400ms was measured on the real cards: the teaser tile's frame was
     not closed at 842ms.

     420ms is where it stops feeling like waiting. The pen is still visibly
     travelling at every sampled frame (it is not a flash), the frame closes
     inside half a second, and it sits just past --t-panel — the slowest thing
     on the site, but no longer the slow thing. Measured across the three
     hosts: 2540 px/s on a room card, 1448 on a pillar, 1188 on a teaser. */
  --t-trace:  420ms;
  --dur:      var(--t-hover);   /* the old name, kept so nothing breaks */

  --header-h: 4.5rem;

  /* ------------------------------------------- floating element stacking
     Everything fixed to the bottom-right corner measures from --stack-bottom
     and steps up by --fab-step. One token decides the clearance, so a change
     to the sticky booking bar cannot leave one button overlapping another.

     Occupants, bottom to top: WhatsApp (always), scroll-to-top (on scroll).
     The sticky booking bar owns the bottom edge below 68rem, so below that
     the whole stack lifts by its height. */
  --fab-size:     3.25rem;
  --fab-gap:      0.75rem;
  --fab-step:     4rem;      /* --fab-size + --fab-gap */
  --stickybar-h:  3.75rem;
  --stack-bottom: var(--gutter);
}
@media (max-width: 67.99rem) {
  :root { --stack-bottom: calc(var(--gutter) + var(--stickybar-h)); }
}

/* ---------------------------------------------------------------- baseline */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--page);
  color: var(--ink-2);
  font-family: var(--font-text);
  font-size: var(--step-0);
  line-height: var(--lh-body);
  font-weight: 400;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  /* 2026-09-25: a korábbi globális `overflow-x: hidden` kikerült — az elrejtette
     a vízszintes túlcsordulást ahelyett, hogy megszüntette volna. Az okokat
     (merev gombfeliratok, rácsoszlop-minimum, hosszú összetett szavak) a
     helyükön javítottuk; a tools/compliance_test.py `mobile` szakasza méri. */
}

h1, h2, h3, h4 {
  font-family: var(--font-display);
  color: var(--ink);
  font-weight: 600;
  line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
  margin: 0;
  text-wrap: balance;
}
p { margin: 0 0 1em; }
p:last-child { margin-bottom: 0; }

a { color: var(--sage-ink); text-decoration-thickness: 1px; text-underline-offset: 0.18em; }
a:hover { color: var(--ink); }

img, svg, iframe { max-width: 100%; }
img { display: block; height: auto; }

/* The `hidden` attribute only hides through the UA rule `[hidden]{display:none}`,
   which ANY author `display` declaration outranks. `.shot` sets `display:block`,
   so the gallery filter was setting `hidden` on every non-matching tile and
   nothing was disappearing — the buttons changed state, the count changed, and
   all 57 photographs stayed on screen.
   Stated once, with !important, so `hidden` means hidden everywhere and no
   future component can silently opt out of it. */
[hidden] { display: none !important; }

button, input, select, textarea { font: inherit; color: inherit; }
button { cursor: pointer; }

:where(a, button, input, select, textarea, summary, [tabindex]):focus-visible {
  outline: 2px solid var(--focus);
  outline-offset: 2px;
  border-radius: var(--radius);
}

::selection { background: var(--sand-3); color: var(--ink); }

.u-visually-hidden {
  position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
  overflow: hidden; clip: rect(0 0 0 0); clip-path: inset(50%); white-space: nowrap;
}

.skip-link {
  position: absolute; left: var(--sp-4); top: -4rem; z-index: 200;
  background: var(--white); color: var(--ink); padding: var(--sp-3) var(--sp-5);
  border-radius: var(--radius); box-shadow: var(--lift-2);
  transition: top var(--dur) var(--ease);
}
.skip-link:focus { top: var(--sp-4); }

/* FILLED, not stroked — and this was the actual reason the pictograms
   "disappeared", far more than their colour.

   All 63 vendored Phosphor icons are filled shapes: the <path> describes the
   solid glyph and the file carries fill="currentColor". The previous rule set
   fill:none and stroke:currentColor, so the browser drew a hairline around
   the silhouette of the glyph instead of the glyph. At a 256-unit viewBox
   rendered near 14px, stroke-width 1.5 works out to 0.08px. There was
   essentially nothing being painted, which is why deepening the colour on its
   own changed nothing.

   Weight now comes from the icon set itself and from size, not from a stroke
   that these shapes were never drawn to have. */
.icon { width: 1.125em; height: 1.125em; flex: none; fill: currentColor; stroke: none; }

/* ------------------------------------------------------------------ layout */
.wrap {
  width: min(100% - (var(--gutter) * 2), 1240px);
  margin-inline: auto;
}
.wrap--text { width: min(100% - (var(--gutter) * 2), var(--measure)); }

.section { padding-block: var(--section-y); }
.section--tight { padding-block: clamp(2.5rem, 5vw, 4rem); }

/* Pages that open on a headline rather than a full-bleed photograph: the
   fixed header has nothing to float over, so <main> clears it. */
body.flat-top main { padding-top: var(--header-h); }

/* The only two tinted grounds in the design. Both are pastel, both are
   lighter than the page's own photography — never a dark break. */
.section--tint { background: var(--sand-1); }
.band          { background: var(--sand-2); padding-block: var(--section-y); }

/* Consecutive same-tone sections would blur into one another, so the second
   one loses its top padding and gains a hairline instead of a colour change. */
.section + .section:not(.section--tint) { padding-top: 0; }
.section--tint + .section--tint { padding-top: 0; }

/* ------------------------------------------------------------- section head
   The signature. An editorial two-column masthead: a full-width hairline,
   the title in the left column, the standfirst in a narrow right column.
   No eyebrow label — v1 repeated the nav word above every section and the
   project's own PRODUCT.md flagged it. */
.sec-head {
  display: grid;
  gap: var(--sp-4) var(--sp-8);
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
  margin-bottom: var(--sp-8);
}
@media (min-width: 60rem) {
  .sec-head { grid-template-columns: minmax(0, 1fr) minmax(0, 0.85fr); align-items: start; }
}
.sec-head__title {
  font-size: var(--step-3);
  letter-spacing: var(--ls-tight);
  max-width: 20ch;
}
.sec-head__lead {
  color: var(--ink-3);
  font-size: var(--step-1);
  max-width: 46ch;
  margin: 0;
}
.sec-head--center {
  justify-items: center; text-align: center; grid-template-columns: 1fr;
}
.sec-head--center .sec-head__title,
.sec-head--center .sec-head__lead { max-width: 52ch; }

/* "See all" sits on the heading row, not as a stray button underneath. */
.sec-head--split { grid-template-columns: minmax(0, 1fr) auto; align-items: end; }
@media (max-width: 47.99rem) { .sec-head--split { grid-template-columns: 1fr; } }
.sec-head__text { display: grid; gap: var(--sp-3); }
.sec-head__action { white-space: nowrap; }

.link-arrow {
  display: inline-flex; align-items: center; gap: 0.45em;
  font-weight: 500; font-size: var(--step--1);
  letter-spacing: 0.01em;
  text-decoration: none;
  border-bottom: 1px solid var(--sage-rule);
  padding-bottom: 2px;
  transition: border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.link-arrow .icon { transition: transform var(--dur) var(--ease); }
@media (hover: hover) {
  .link-arrow:hover { border-color: var(--sage-deep); }
  .link-arrow:hover .icon { transform: translateX(3px); }
}

/* ----------------------------------------------------------------- buttons */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 0.5em;
  padding: 0.72rem 1.4rem;
  border: 1px solid transparent;
  border-radius: var(--radius);
  font-family: var(--font-text);
  font-size: var(--step--1);
  font-weight: 600;
  letter-spacing: 0.005em;
  text-decoration: none;
  white-space: nowrap;
  transition: background-color var(--t-hover) var(--ease),
              border-color var(--t-hover) var(--ease),
              color var(--t-hover) var(--ease),
              box-shadow var(--t-hover) var(--ease),
              transform var(--t-press) var(--ease-out);
}
/* Instant, and slightly faster than the hover it interrupts: the press is the
   system answering, and an answer should never feel deliberate. */
.btn:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  /* One pixel. Enough to register as the button coming to meet the cursor,
     not enough to make a row of them look like it is breathing. */
  .btn:hover { transform: translateY(-1px); }
  .btn:active:hover { transform: scale(0.97); }
  .btn--primary:hover { box-shadow: var(--lift-1); }
}

.btn--primary { background: var(--sage-deep); color: var(--white); }
.btn--primary:hover { background: var(--sage-deeper); color: var(--white); }

/* Outline. On the page and on any pastel tint alike. */
.btn--ghost { background: transparent; color: var(--ink); border-color: var(--line-strong); }
.btn--ghost:hover { background: var(--sand-1); border-color: var(--sage-deep); color: var(--ink); }

/* On a photograph. Solid white so it never depends on what is behind it. */
.btn--onphoto { background: rgba(255,255,255,0.94); color: var(--ink); }
.btn--onphoto:hover { background: var(--white); color: var(--ink); }

.btn--quiet { background: transparent; color: var(--sage-ink); padding-inline: 0.25rem; }
/* Kisebb gomb a kártyákon (hozzájárulás, térkép-helykitöltő, foglalás közbeni belépés).
   white-space: normal — hosszabb nyelveken (DE/PL) a felirat törhessen, ne lógjon ki. */
.btn--sm { padding: 0.5rem 0.9rem; font-size: var(--step--2); white-space: normal; text-align: center; }
.btn--quiet:hover { color: var(--ink); }

.btn--full { width: 100%; }
/* Soha ne legyen szélesebb a tartójánál. Keskeny kijelzőn a felirat tördelhet:
   a nowrap felirat min-content szélessége különben a rácsoszlopot is kitolja
   (SK ajánlatkérő űrlap, 320 px). A rövid feliratok ettől nem törnek meg. */
.btn { max-width: 100%; }
@media (max-width: 30rem) { .btn { white-space: normal; text-align: center; } }
/* Rácsban a gyermek min-width: auto = a tartalma legszélesebb eleme — ez vitte
   ki a kapcsolat-rácsot. A tartalom így a sávhoz igazodik. */
.contact > * { min-width: 0; }
/* Hosszú összetett szavak (DE „Geschäftsbedingungen") a nagy címben. */
.acc-head__title, .legal h2 { overflow-wrap: break-word; hyphens: auto; }
.btn--lg { padding: 0.9rem 1.9rem; font-size: var(--step-0); }

/* ------------------------------------------------------------------ header
   A translucent white bar with dark ink, from the very top — never white text
   floating on the photograph.

   The transparent-over-photo header is the obvious choice for an immersive
   hero and it is the wrong one here, because it is not survivable. Measured
   across all nine hero photographs, the brightest strip under the header is
   rgb(232,237,250) — pool water against sky — where white nav text scores
   1.17:1. Rescuing it needs a dark band at alpha 0.60 across the top of every
   hero, which is precisely the heavy overlay this design exists to avoid.

   A near-white bar inverts the problem: dark ink on a light ground is 9:1
   regardless of what photograph is behind it, the bar reads as light rather
   than as a shadow, and the photograph below it is left completely alone. */
.header {
  position: fixed; inset: 0 0 auto 0; z-index: 90;
  height: var(--header-h);
  display: flex; align-items: center;
  background: rgba(255, 254, 252, 0.82);
  backdrop-filter: blur(16px) saturate(1.1);
  -webkit-backdrop-filter: blur(16px) saturate(1.1);
  transition: background-color 260ms var(--ease), box-shadow 260ms var(--ease);
}
.header__bar { display: flex; align-items: center; gap: var(--sp-5); width: min(100% - (var(--gutter) * 2), 1240px); margin-inline: auto; }

/* Once the page has scrolled there is no photograph to hint at, so the bar
   becomes fully opaque and grows a hairline. */
.header[data-solid="true"] { background: rgba(255,254,252,0.97); box-shadow: 0 1px 0 var(--line); }

/* No backdrop-filter (older Firefox, some Android): fall back to a solid bar
   rather than a see-through one, because the see-through one is the unsafe
   state. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .header { background: rgba(255, 254, 252, 0.97); }
}

.brand { display: grid; gap: 0.24rem; text-decoration: none; margin-right: auto; line-height: 1.1; }
/* The wordmark image replaces what .brand__name used to set in type. Height
   drives it and width is auto, so the intrinsic 349x60 never distorts; the
   width/height attributes on the tag reserve the box so the bar does not
   reflow as it loads. The logo is pure black on transparency and every ground
   it sits on here is light (header rgba(255,254,252,.82+), footer --sand-1),
   so no inverted variant is needed — measured, not assumed. */
.brand__logo { height: 22px; width: auto; display: block; }
/* Kept: .brand__name is no longer emitted, but the admin harness and the v1
   fallback page still reference the class. Cheap to leave correct. */
.brand__name {
  font-family: var(--font-display); font-size: 1.3125rem; font-weight: 700;
  letter-spacing: var(--ls-tight); color: var(--ink);
}
.brand__sub {
  /* --ink-2, not --ink-3: this sits on the translucent header bar, which a
     dark photograph drags down. --ink-3 measures 3.58:1 there. */
  font-size: var(--step--2); color: var(--ink-2); letter-spacing: 0.08em;
  text-transform: uppercase; font-weight: 500;
}
/* The strapline is the widest thing in the brand block — 232px in Polish
   against the wordmark's 128 — and the bar is capped at 1240px however wide
   the window is. Measured with seven nav items and nothing wrapping, Polish
   was over budget by 81px, German by 70 and Hungarian by 55; reclaiming this
   line covers all three.
   It is the right thing to drop because it is the only REDUNDANT thing in the
   bar: the logo directly above it already reads PÁTER, and the full
   "Apartmanok és Szobák · Siófok" still shows in the footer, in the drawer and
   in the page title. Below the nav's own breakpoint the bar is just brand and
   burger, so there it stays. */
@media (min-width: 68rem) { .brand__sub { display: none; } }
/* The header ink never changes with scroll position. That is the point: there
   is no state in which it is white text on an unknown photograph. */

.nav { display: none; }
@media (min-width: 68rem) {
  .nav { display: block; }
  /* A little air between the last nav link and the cluster on the right. The
     row gained two more objects, and without this the links, the language
     switch, the social pair and the booking button read as one dense run
     pushed against the edge. It moves the nav left; it does not re-lay the
     header out. */
  .nav { margin-inline-end: var(--sp-3); }
}
.nav__list { display: flex; gap: var(--sp-5); list-style: none; margin: 0; padding: 0; }
/* A nav label is a phrase, not prose: it may never break mid-item. Without
   this the Polish bar rendered "Strona / główna" and "Zapytaj o / ofertę"
   stacked over two and three lines, which grew the header to 79px. */
.nav__link { white-space: nowrap; }
.nav__link {
  position: relative;
  color: var(--ink-2); text-decoration: none; font-size: var(--step--1); font-weight: 500;
  padding-block: 0.4rem;
  transition: color var(--t-hover) var(--ease);
}
/* The underline was a border whose colour faded in, which reads as the rule
   appearing rather than being drawn. It is a scaled pseudo-element now, so it
   wipes out from the left edge of the word — and scaleX is a compositor
   property, where border-color is a paint. */
.nav__link::after {
  content: ""; position: absolute; left: 0; right: 0; bottom: 0; height: 1px;
  background: var(--sage-deep);
  transform: scaleX(0); transform-origin: left center;
  transition: transform var(--t-hover) var(--ease-out);
}
.nav__link[aria-current="page"] { color: var(--ink); }
.nav__link[aria-current="page"]::after { transform: scaleX(1); }
@media (hover: hover) and (pointer: fine) {
  .nav__link:hover { color: var(--ink); }
  .nav__link:hover::after { transform: scaleX(1); }
}
/* Keyboard users get the same affordance. */
.nav__link:focus-visible::after { transform: scaleX(1); }

.langs { display: none; gap: 0.15rem; }
@media (min-width: 68rem) { .langs { display: flex; } }
.langs__btn {
  font-size: var(--step--2); font-weight: 600; letter-spacing: 0.04em;
  color: var(--ink-2); text-decoration: none; padding: 0.3rem 0.42rem;
  border-radius: var(--radius);
  transition: color var(--dur) var(--ease), background-color var(--dur) var(--ease);
}
.langs__btn:hover { color: var(--ink); background: var(--sand-1); }
.langs__btn[aria-current="true"] { color: var(--white); background: var(--sage-deep); }

/* Facebook and TikTok beside the booking button. Secondary by construction:
   no label, no background, no border — a 1rem glyph in the same meta ink the
   nav links use, next to a filled CTA. They are findable, and they do not
   compete.

   Hidden below 68rem, on the same breakpoint as the nav and the language
   switch. Between 48 and 68rem the bar is already brand + button + burger and
   two more targets would crowd it; below 48rem there is no room at all. The
   drawer carries the same pair instead, so nothing is lost on a phone. */
.header__social { display: none; gap: var(--sp-2); align-items: center; }
@media (min-width: 68rem) { .header__social { display: flex; } }
.header__social__link {
  display: inline-flex; align-items: center; justify-content: center;
  width: 1.85rem; height: 1.85rem;
  color: var(--ink-3);
  transition: color var(--t-hover) var(--ease),
              transform var(--t-hover) var(--ease-out);
}
.header__social__glyph { width: 1rem; height: 1rem; }
@media (hover: hover) and (pointer: fine) {
  /* The same two moves every other link on this site makes on hover, scaled
     to a 16px glyph: it takes the accent ink and lifts a single pixel. */
  .header__social__link:hover { color: var(--sage-deep); transform: translateY(-1px); }
}
.header__social__link:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }

/* In the drawer they sit above the two full-width buttons, as a row of two
   larger targets — a thumb needs more than 16px. */
.drawer__social { display: flex; gap: var(--sp-3); margin-bottom: var(--sp-2); }
.drawer__social__link {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.75rem; height: 2.75rem;
  border: 1px solid var(--line); color: var(--ink-2);
  transition: color var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease);
}
.drawer__social__glyph { width: 1.15rem; height: 1.15rem; }
.drawer__social__link:hover { color: var(--sage-deep); border-color: var(--sage-deep); }

.header__cta { display: none; }
@media (min-width: 48rem) { .header__cta { display: inline-flex; } }

.burger {
  display: inline-flex; align-items: center; justify-content: center;
  width: 2.5rem; height: 2.5rem; border: 0; background: transparent;
  color: var(--ink); border-radius: var(--radius);
}
.burger .icon { width: 1.5rem; height: 1.5rem; }
@media (min-width: 68rem) { .burger { display: none; } }

/* ------------------------------------------------------------ sticky CTA
   The persistent, unobtrusive way to book from any scroll position. A slim
   bar, not a banner: it appears only after the hero has left, carries one
   filled and one outline action, and never covers content because the page
   reserves nothing for it — it sits at the bottom edge on mobile and folds
   into the header on desktop. */
.stickybar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 85;
  display: flex; align-items: center; gap: var(--sp-3);
  padding: 0.6rem var(--gutter) calc(0.6rem + env(safe-area-inset-bottom));
  background: rgba(255,254,252,0.94); backdrop-filter: blur(12px);
  border-top: 1px solid var(--line);
  transform: translateY(110%);
  transition: transform 320ms var(--ease-out);
}
.stickybar[data-show="true"] { transform: translateY(0); }
.stickybar__text { display: none; margin: 0; margin-right: auto; }
.stickybar__text a {
  display: inline-flex; align-items: center; gap: 0.45em;
  font-size: var(--step--1); font-weight: 600; color: var(--ink); text-decoration: none;
}
.stickybar__text a:hover { color: var(--sage-ink); }
.stickybar__text .icon { color: var(--sage-deep); }
/* flex: 1 alapból min-width: auto — nowrap-pal ez a teljes felirat szélessége,
   és a két gomb együtt kitolta a lapot (PL 320 px). A gombok osztoznak a soron,
   a felirat tördelhet. */
.stickybar .btn { flex: 1 1 0; min-width: 0; white-space: normal; text-align: center; }
@media (min-width: 48rem) {
  .stickybar__text { display: block; }
  .stickybar .btn { flex: none; }
}
/* On wide screens the header already carries a permanent booking button, so
   the bar would be a second copy of the same thing. */
@media (min-width: 68rem) { .stickybar { display: none; } }

/* ------------------------------------------------------------------ drawer */
.drawer { position: fixed; inset: 0; z-index: 100; visibility: hidden; }
.drawer[data-open="true"] { visibility: visible; }
.drawer__scrim {
  position: absolute; inset: 0; background: rgba(33,29,24,0.32);
  opacity: 0; transition: opacity 260ms var(--ease);
}
.drawer[data-open="true"] .drawer__scrim { opacity: 1; }
.drawer__panel {
  position: absolute; inset: 0 0 0 auto; width: min(22rem, 88vw);
  background: var(--page); box-shadow: var(--lift-3);
  display: flex; flex-direction: column;
  transform: translateX(100%);
  transition: transform 300ms var(--ease-out);
  overflow-y: auto;
}
.drawer[data-open="true"] .drawer__panel { transform: none; }
.drawer__head {
  display: flex; align-items: center; justify-content: space-between;
  padding: var(--sp-5) var(--sp-5); border-bottom: 1px solid var(--line);
}
.drawer__logo { height: 24px; width: auto; display: block; }
.drawer__close { border: 0; background: transparent; color: var(--ink); padding: var(--sp-2); border-radius: var(--radius); }
.drawer__close .icon { width: 1.4rem; height: 1.4rem; }
.drawer__list { display: grid; padding: var(--sp-3) 0; }
.drawer__link {
  display: flex; align-items: center; justify-content: space-between;
  padding: 0.8rem var(--sp-5); color: var(--ink-2); text-decoration: none;
  font-size: var(--step-0); font-weight: 500;
}
.drawer__link .icon { color: var(--ink-3); width: 1rem; height: 1rem; }
.drawer__link:hover { background: var(--sand-1); color: var(--ink); }
.drawer__link[aria-current="page"] { color: var(--ink); box-shadow: inset 3px 0 0 var(--sage-deep); }
.drawer__foot { margin-top: auto; display: grid; gap: var(--sp-3); padding: var(--sp-5); border-top: 1px solid var(--line); }
.drawer__langs { display: flex; gap: var(--sp-2); justify-content: center; padding-bottom: var(--sp-4); }
.drawer__langs .langs__btn { display: inline-flex; }

/* -------------------------------------------------------------------- hero
   The photograph is the design. No duotone, no colour wash, no heavy
   overlay — only a gradient in the lower third where the headline sits. */
.hero {
  position: relative;
  min-height: min(94svh, 46rem);
  display: flex; align-items: flex-end;
  padding-top: var(--header-h);
  padding-bottom: clamp(3rem, 7vw, 5.5rem);
  overflow: hidden;
  isolation: isolate;
  background: var(--sand-1);
}
/* ---------------------------------------------------------------- masthead
   Every subpage header: a full-bleed banner with the words ON the photograph.
   Structurally the homepage hero, shorter and page-specific.

   Three earlier attempts are worth recording, because each failed for its own
   reason and the third one failed in an instructive direction.

   1. The homepage hero reused verbatim: same heavy dark scrim, so the one
      dark element on the site sat on top of ten light pages.
   2. A band with the text lifted onto a white plate beside it. Legible, but
      it forced 4:3 and 3:2 photographs through a 2.9:1 slot and threw away
      more than half of each. One EXIF-rotated file showed 23% of itself.
   3. A panel whose aspect came from the photograph, text beside it. That did
      fix the cropping — and stopped being a header. It was a two-column
      content block: title in a column, photo in a box next to it.

   So: back to a banner, but with what the other rounds bought. The photograph
   is landscape by assertion (the builder refuses a portrait one), its focal
   point is named per file, the scrim is measured rather than guessed, and it
   is masked away horizontally so only the text's own corner is darkened. */
.masthead {
  position: relative;
  /* The brief's range: a real banner, not a strip, and shorter than the
     homepage hero. 320px at a small laptop, 440px at 1440.

     --band-k adds up to 6rem of that on top, and only for photographs tight
     enough to need it (pages.masthead computes it from the source aspect; 3:2
     and wider get 0). A 4:3 interior through the base band kept 41% of its
     height, which on /szobaink/ was two headboards and a duvet and no room.
     At the full bonus the same picture keeps 53% — enough to see where you
     are. Landscape photographs are unchanged, so the pool banners still crop
     exactly as they were signed off. */
  min-height: calc(clamp(20rem, 32vw, 27.5rem) + var(--band-k, 0) * 6rem);
  display: flex; align-items: flex-end;
  padding-top: var(--header-h);
  padding-bottom: clamp(1.75rem, 3.4vw, 2.75rem);
  overflow: hidden;
  isolation: isolate;
  background: var(--sand-2);
}
.masthead__media { position: absolute; inset: 0; z-index: -2; }
.masthead__img {
  width: 100%; height: 100%; object-fit: cover;
  object-position: 50% 45%;   /* overridden per photograph from focal.json */
}

/* The only darkening on any subpage, and it is measured per photograph rather
   than fixed for all of them.

   It used to be one gradient calibrated against the brightest ground any
   headline could land on — pool water at rgb(228,226,225). Correct for the
   pool, ruinous for a pale interior, which had nothing left to show through
   74% of rgb(28,24,20): /franciaagyas-szoba/ measured 0.23 relative luminance
   and read as the one dark section on a light site.

   So --veil-k scales every stop, and it comes from the image. Each banner's
   headline rectangle is sampled at its real crop and the multiplier solved for
   5:1 white-on-photo — tools/measure_banners.py writes content/banners.json,
   tools/audit.py re-derives the contrast from it and fails the build if a
   photograph changed without being re-measured. The pool keeps ~1.0; the
   interiors come in around 0.78, which is a photograph again. */
.masthead__veil {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(to top,
    rgb(28 24 20 / calc(0.74 * var(--veil-k, 1))) 0%,
    rgb(28 24 20 / calc(0.68 * var(--veil-k, 1))) 30%,
    rgb(28 24 20 / calc(0.60 * var(--veil-k, 1))) 50%,
    rgb(28 24 20 / calc(0.30 * var(--veil-k, 1))) 70%,
    rgb(28 24 20 / 0)                             90%);
}
/* Localised horizontally once there is room to be. The headline column ends
   around half the container, so past that the scrim fades out and the right
   half of every photograph is shown untouched. This is what keeps a legible
   headline from becoming the dark hero overlay the whole design avoids. */
@media (min-width: 60rem) {
  .masthead__veil {
    -webkit-mask-image: linear-gradient(to right, #000 0%, #000 52%, transparent 88%);
            mask-image: linear-gradient(to right, #000 0%, #000 52%, transparent 88%);
  }
}

.masthead__inner { position: relative; color: var(--white); }
.masthead__title {
  color: var(--white);
  font-size: var(--step-4);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  font-weight: 600;
  max-width: 18ch;
  margin: 0;
  text-shadow: 0 2px 22px rgba(28, 24, 20, 0.45);
}
.masthead__lead {
  margin: var(--sp-4) 0 0;
  max-width: 46ch;
  font-size: var(--step-1);
  color: rgba(255, 255, 255, 0.94);
  text-shadow: 0 1px 14px rgba(28, 24, 20, 0.5);
}

/* Ancestor path, overlaid with the rest. Sentence case and a real link, not
   the uppercase wide-tracked label that sits above every heading on a
   generated page. */
.crumbs {
  display: flex; flex-wrap: wrap; align-items: center; gap: 0.35em;
  font-size: var(--step--1);
  color: rgba(255, 255, 255, 0.95);
  margin: 0 0 var(--sp-4);
  text-shadow: 0 1px 10px rgba(28, 24, 20, 0.55);
}
.crumbs a {
  color: var(--white); text-decoration: none;
  border-bottom: 1px solid rgba(255, 255, 255, 0.4);
}
.crumbs a:hover { border-bottom-color: var(--white); }
.crumbs__sep { width: 0.8em; height: 0.8em; color: rgba(255, 255, 255, 0.6); }
.crumbs [aria-current] { color: rgba(255, 255, 255, 0.95); }

@media (max-width: 47.99rem) {
  .masthead { min-height: 17rem; }
  .masthead__title { font-size: var(--step-3); max-width: none; }
}

/* Motion. Two moves, each with a job.
   1. The photograph settles: the page has arrived somewhere.
   2. The words rise a beat later: the picture establishes the place, then the
      words name it. That sequence is the hierarchy.
   Nothing loops, nothing waits on JavaScript. */
@media (prefers-reduced-motion: no-preference) {
  body[data-entrance="on"] .masthead__img {
    animation: mastheadSettle 1100ms var(--ease-out) both;
  }
  body[data-entrance="on"] .crumbs { animation: heroRise 700ms var(--ease-out) 140ms both; }
  body[data-entrance="on"] .masthead__title { animation: heroRise 700ms var(--ease-out) 200ms both; }
  body[data-entrance="on"] .masthead__lead { animation: heroRise 700ms var(--ease-out) 280ms both; }
}
@keyframes mastheadSettle {
  from { transform: scale(1.05); opacity: 0; }
  45%  { opacity: 1; }
  to   { transform: none; opacity: 1; }
}

/* 3. A slow drift as the banner leaves, for depth. Driven by the scroll
   timeline itself, so there is no scroll listener and no main-thread work;
   browsers without it get a still photograph. The 4% of scale is the travel
   room, and it is the only magnification in the component.

   The timeline is named on `.masthead`, not taken as an anonymous `view()` on
   the image. `view()` measures its own element against the nearest ancestor
   *scroll container*, and `.masthead` is one: it carries `overflow: hidden`
   for the crop. The image fills that box exactly, so its progress inside it
   pinned at 50% and stayed there however far the page scrolled — the banner
   sat frozen at `scale(1.04)`, a permanent 4% crop-in that never resolved.
   Naming the timeline on `.masthead` measures the banner against the viewport
   instead, which is the thing the drift was always about. */
@supports (animation-timeline: view()) {
  @media (prefers-reduced-motion: no-preference) {
    .masthead { view-timeline-name: --masthead-view; }
    .masthead__img {
      animation: mastheadDrift linear both;
      animation-timeline: --masthead-view;
    }
  }
}
@keyframes mastheadDrift {
  from { transform: scale(1.04) translateY(-1.6%); }
  to   { transform: scale(1.04) translateY(1.6%); }
}

/* The room pages' own header content: tags, price, two buttons. It sits under
   the banner at full width, as a normal content row, because a banner that
   also has to hold a price stops being a banner. */
.roombar {
  border-bottom: 1px solid var(--line);
  padding-block: var(--sp-6);
}
.roombar__inner {
  display: flex; flex-wrap: wrap; align-items: center;
  gap: var(--sp-5) var(--sp-6);
}
.roombar__facts { display: grid; gap: var(--sp-4); flex: 1 1 22rem; }
.roombar .price { margin: 0; }
/* `0 0 auto` kept the pair rigid, so at 390px the second button ran off the
   edge. They wrap instead. */
.roombar .hero__actions { margin: 0; flex: 1 1 auto; justify-content: flex-end; }
@media (max-width: 47.99rem) {
  .roombar .hero__actions { justify-content: flex-start; }
  .roombar .hero__actions .btn { flex: 1 1 12rem; justify-content: center; }
}

.hero__stage { position: absolute; inset: 0; z-index: -2; }
.hero__slide {
  position: absolute; inset: 0;
  opacity: 0;
  transition: opacity 1100ms var(--ease);
}
.hero__slide[data-active="true"] { opacity: 1; }
/* Biased upward, not centred. A hero slot is roughly 2.4:1 and these
   photographs are 3:2 and 4:3, so cover() throws away a lot of height —
   centred, it keeps the paving in the foreground and crops the pool and the
   building out of the top. 38% keeps the subject and drops the ground. */
.hero__slide img { width: 100%; height: 100%; object-fit: cover; object-position: 50% 38%; }

/* The only darkening in the whole design, and it is measured rather than
   eyeballed. tools/audit.py samples the actual hero photographs: the
   brightest ground any headline lands on is the pool water at rgb(228,226,225),
   and white text needs alpha >= 0.55 over it to clear 4.5:1. The gradient
   therefore holds 0.58 across the whole text band and only then falls away,
   so the top half of every photograph — sky, water, building — is untouched.

   An earlier draft faded to 0.42 by 22%, which measured 3.30:1 on that same
   water and would have made the standfirst unreadable on two of the five
   slides. The audit caught it; nothing about it was visible in a thumbnail. */
.hero__veil {
  position: absolute; inset: 0; z-index: -1; pointer-events: none;
  background: linear-gradient(to top,
    rgba(28, 24, 20, 0.74) 0%,
    rgba(28, 24, 20, 0.68) 24%,
    rgba(28, 24, 20, 0.58) 50%,
    rgba(28, 24, 20, 0.28) 66%,
    rgba(28, 24, 20, 0.00) 82%);
}
/* Localised horizontally too, once there is room to be. The headline column
   ends around 50% of a 1240px container, so past that the gradient fades out
   and the right half of every photograph — pool, water, building — is shown
   completely untouched. This is what keeps a legible headline from turning
   into the "dark hero overlay" the whole design is trying to avoid. Narrow
   screens keep the full-width gradient, because there the text is full width. */
@media (min-width: 60rem) {
  .hero__veil {
    -webkit-mask-image: linear-gradient(to right, #000 0%, #000 54%, transparent 88%);
            mask-image: linear-gradient(to right, #000 0%, #000 54%, transparent 88%);
  }
}

/* The inner pages carry one short line of text, so their gradient can be
   shorter and lighter than the homepage's. */
.hero--page .hero__veil {
  background: linear-gradient(to top,
    rgba(28, 24, 20, 0.70) 0%,
    rgba(28, 24, 20, 0.60) 34%,
    rgba(28, 24, 20, 0.30) 58%,
    rgba(28, 24, 20, 0.00) 78%);
}

.hero__content { position: relative; color: var(--white); }
.hero__flag {
  display: inline-flex; align-items: center; gap: 0.45em;
  font-size: var(--step--1); font-weight: 500; letter-spacing: 0.04em;
  color: rgba(255,255,255,0.94); margin: 0 0 var(--sp-4);
  text-shadow: 0 1px 10px rgba(28,24,20,0.55);
}
.hero__title {
  color: var(--white);
  font-size: var(--step-5);
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-tight);
  font-weight: 600;
  max-width: 17ch;
  text-shadow: 0 2px 26px rgba(28,24,20,0.42);
}
.hero__title em { font-style: normal; color: var(--sage); }
.hero__title--page { max-width: 20ch; font-size: var(--step-4); }
.hero__lead {
  margin: var(--sp-4) 0 0;
  max-width: 46ch;
  font-size: var(--step-1);
  color: rgba(255,255,255,0.94);
  text-shadow: 0 1px 16px rgba(28,24,20,0.5);
}
.hero__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-6); }
/* A hosszú fordítások (PL „Rezerwuj z natychmiastowym potwierdzeniem", SK, HU)
   nowrap-pal szélesebbek a 320–412 px-es képernyőnél, és vízszintes görgetést
   okoztak (launch-audit 06). A gomb itt a sor szélességéig nőhet, a felirat
   pedig tördelhet — a hiba oka a merev tartalomszélesség, nem a lap. */
.hero__actions .btn,
.cta-panel__actions .btn { max-width: 100%; white-space: normal; text-align: center; text-wrap: balance; }

.hero__dots {
  position: absolute; right: var(--gutter); bottom: clamp(3rem, 7vw, 5.5rem);
  display: flex; gap: 0.4rem; z-index: 2;
  /* The dots sit on the right, which is exactly where the veil is masked out
     so the photograph can be seen clean — so they have no dark backing and
     white-on-white can swallow them. They carry their own shadow instead. */
  filter: drop-shadow(0 1px 3px rgba(28, 24, 20, 0.65));
}
.hero__dot {
  width: 0.5rem; height: 0.5rem; padding: 0; border: 0; border-radius: 50%;
  background: rgba(255,255,255,0.62);
  transition: background-color var(--dur) var(--ease), transform var(--dur) var(--ease);
}
.hero__dot[aria-current="true"] { background: var(--white); transform: scale(1.35); }
@media (max-width: 47.99rem) { .hero__dots { display: none; } }

/* Entrance. Light-based: the photo settles and the words rise into it. Runs
   once per session (site.js sets the attribute), never on internal nav. */
@media (prefers-reduced-motion: no-preference) {
  body[data-entrance="on"] .hero__slide[data-active="true"] img {
    animation: heroSettle 1400ms var(--ease-out) both;
  }
  body[data-entrance="on"] .hero__flag  { animation: heroRise 700ms var(--ease-out) 120ms both; }
  body[data-entrance="on"] .hero__title { animation: heroRise 700ms var(--ease-out) 200ms both; }
  body[data-entrance="on"] .hero__lead  { animation: heroRise 700ms var(--ease-out) 300ms both; }
  body[data-entrance="on"] .hero__actions { animation: heroRise 700ms var(--ease-out) 380ms both; }
  body[data-entrance="on"] .searchbar   { animation: heroRise 800ms var(--ease-out) 460ms both; }
}
@keyframes heroSettle {
  from { transform: scale(1.06); opacity: 0; }
  40%  { opacity: 1; }
  to   { transform: none; opacity: 1; }
}
@keyframes heroRise {
  from { opacity: 0; transform: translateY(14px); }
  to   { opacity: 1; transform: none; }
}

/* --------------------------------------------------------------- searchbar
   Lifted onto the hero's lower edge so the first action is reachable without
   scrolling, while the photograph still owns the frame above it. */
.searchbar {
  position: relative; z-index: 3;
  margin-top: calc(clamp(2rem, 5vw, 3.5rem) * -1);
  margin-bottom: var(--sp-6);
}
/* On a room page there is no hero photograph to float over, so the picker
   sits in the flow instead of being pulled up onto one. */
.searchbar--flat { margin-top: 0; margin-bottom: 0; }
.searchbar__inner {
  display: grid; gap: var(--sp-4);
  grid-template-columns: 1fr;
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  box-shadow: var(--lift-2);
  padding: var(--sp-5);
}
@media (min-width: 56rem) {
  .searchbar__inner {
    grid-template-columns: repeat(3, minmax(0, 1fr)) auto;
    align-items: end;
    gap: var(--sp-5);
  }
  .searchbar__hint { grid-column: 1 / -1; }
}
.searchbar__field { display: grid; gap: 0.35rem; }
.searchbar__label {
  font-size: var(--step--2); font-weight: 600; letter-spacing: 0.06em;
  text-transform: uppercase; color: var(--ink-3);
}
/* The CTA is part of the same row, so it takes the same height. Its label is
   13px against the fields' 16px, which left it 6.3px shorter than the inputs
   beside it — the one control in the row a guest is meant to aim at. */
.searchbar__submit { align-self: end; height: var(--control-h); }
.searchbar__hint { margin: 0; font-size: var(--step--2); color: var(--ink-3); }

.claims {
  display: flex; flex-wrap: wrap; gap: var(--sp-3) var(--sp-6);
  list-style: none; margin: var(--sp-5) 0 0; padding: 0;
  justify-content: center;
}
.claims__item {
  display: inline-flex; align-items: center; gap: 0.45em;
  font-size: var(--step--1); color: var(--ink-3);
}
.claims__item .icon { color: var(--sage-deep); }
/* The .rating-badge block stood here until 2026-08-28: a hairline pill
   carrying the Booking.com score, used in the hero claim row and beside the
   reviews heading. Booking.com was dropped as a reference, both call sites
   went with it, and the rules would otherwise have become dead weight that
   still reads as intentional to the next person editing this file. */

/* ------------------------------------------------------------------ intro
   An editorial standfirst: one large, slow paragraph. This is the "linger"
   moment the brief asks for — nothing to click, just the place described. */
.intro { display: grid; gap: var(--sp-6); }
@media (min-width: 60rem) {
  .intro { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: var(--sp-9); align-items: start; }
}
.intro__lead {
  font-family: var(--font-display);
  font-size: var(--step-2);
  line-height: 1.42;
  letter-spacing: var(--ls-head);
  color: var(--ink);
  font-weight: 400;
  text-wrap: pretty;
}
.intro__body { color: var(--ink-2); }

/* ---------------------------------------------------------------- pillars
   Three equal columns, each led by its own photograph. Still no boxes and no
   shadows — the picture and the hairline-free whitespace do the separating,
   so "why us" reads as three pieces of evidence rather than as a feature
   matrix of identical cards. */
.pillars { display: grid; gap: var(--sp-7); }
@media (min-width: 56rem) {
  .pillars { grid-template-columns: repeat(3, minmax(0, 1fr)); gap: var(--sp-7); }
}
.pillar { display: grid; gap: var(--sp-3); align-content: start; }
/* `position: relative` so the shared .photofx overlay has something to sit in;
   the radius and overflow it needs were already here. */
.pillar__figure {
  position: relative;
  margin: 0 0 var(--sp-2); border-radius: var(--radius);
  overflow: hidden; background: var(--sand-1);
}
/* 3:2 rather than 4:3: at a third of the container these sit beside body text,
   and the shallower crop keeps them from towering over the words. */
.pillar__figure img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.pillar__icon { color: var(--sage-deep); display: flex; }
.pillar__icon .icon { width: 1.4rem; height: 1.4rem; }
.pillar__title { font-size: var(--step-2); }
.pillar__body { color: var(--ink-3); margin: 0; font-size: var(--step-0); }

/* ---------------------------------------------------------------- factband
   Three numbers on a sand tint. v1 had these as "stat chips" (big number +
   small label in a bordered pill), which PRODUCT.md flagged as the hero-metric
   template. Here they are set as three quiet display figures separated by
   rules, with no chrome at all. */
.factband { background: var(--sand-1); padding-block: clamp(3rem, 6vw, 5rem); }
.factband__grid { display: grid; gap: var(--sp-6); }
@media (min-width: 48rem) { .factband__grid { grid-template-columns: repeat(3, 1fr); gap: var(--sp-8); } }
.factband__item { display: grid; gap: var(--sp-1); }
@media (min-width: 48rem) {
  .factband__item + .factband__item { border-left: 1px solid var(--sand-3); padding-left: var(--sp-8); }
}
.factband__num {
  font-family: var(--font-display); font-size: clamp(2.25rem, 4vw, 3rem);
  font-weight: 500; line-height: 1; letter-spacing: var(--ls-tight); color: var(--ink);
  font-variant-numeric: tabular-nums;
}
.factband__label { font-size: var(--step--1); color: var(--ink-3); max-width: 22ch; }

/* ------------------------------------------------------------------- rooms
   An editorial stack, not a four-up card grid. Each room is a wide row that
   alternates side, so the eye travels down the page rather than scanning a
   matrix. Every row carries its own quote CTA — the brief's "per room card"
   requirement — without a button crowding the photograph. */
.rooms { display: grid; gap: clamp(3rem, 6vw, 5rem); }
.room { display: grid; gap: var(--sp-5); align-items: center; }
@media (min-width: 56rem) {
  /* The photograph takes the larger share. The brief puts the pictures first,
     and a 50/50 split makes a room read as a spec sheet with an illustration. */
  .room { grid-template-columns: minmax(0, 1.32fr) minmax(0, 1fr); gap: clamp(2.5rem, 4.5vw, 4rem); }
  .room:nth-child(even) .room__media { order: 2; }
}
.room__media {
  position: relative; display: block; border-radius: var(--radius); overflow: hidden;
  background: var(--sand-1); text-decoration: none;
  transition: transform var(--t-hover) var(--ease-out),
              box-shadow var(--t-hover) var(--ease);
}
.room__media img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
/* Hover may only ever move the photograph that is already there. An earlier
   build stacked a second <img class="room__img--alt"> on top and crossfaded to
   it, so pointing at a card silently showed a different room. Both the markup
   and the rule are gone; this scale is the whole hover effect. */
/* Gated on a real pointer: a touch device fires :hover on tap, so an ungated
   hover leaves the card stuck in its hovered state after a finger lifts.
   Everything here acts on the SINGLE image the card has — there is no second
   image to cross-fade to, and check_room_cards() in the audit fails the build
   if one ever comes back. */
/* The LIFT is scoped to the photograph too. It used to fire from `.room:hover`,
   which meant resting the cursor on the description visibly raised the picture
   and gave it a shadow — the image reacting to something that was not about
   the image. Now every response the photograph makes (lift, zoom, dim, frame)
   shares one trigger. The arrow still answers to the card, because it belongs
   to the link text rather than to the picture. */
@media (hover: hover) and (pointer: fine) {
  .room__media:hover { transform: translateY(-3px); box-shadow: var(--lift-2); }
  .room:hover .link-arrow .icon { transform: translateX(3px); }
}


/* ------------------------------------------------------- photo hover fx
   The shared hover treatment for a photograph in a card: the picture dims a
   little, and a hairline frame draws itself around it, inset like matting.

   ONE component, several hosts. A host supplies `fx-photo` on whatever should
   answer to :hover — normally the whole card, so the text counts as part of
   the target — and puts the markup inside the element holding the photograph.
   Currently the room cards and the three "why us" pillars. It is deliberately
   NOT on the gallery grid: those tiles already have a considered treatment of
   their own that says "this opens larger", which is a different promise.

   The dim and the frame are one <svg>, so they fade as a single object rather
   than as two overlays that could drift apart. Both are pointer-transparent;
   whatever is underneath keeps the click.

   Geometry in real pixels. The <svg> carries no viewBox, so one user unit is
   one CSS pixel and the frame's corner radius can be derived from the card's
   own at every width. Through a viewBox the radius would have scaled with the
   box, from a crisp radius on a phone to a fat arc on a desktop.

   The dash figures are hundredths, not pixels: `pathLength="100"` on the rect
   renormalises the perimeter, so `stroke-dasharray: 100` is exactly one lap
   whatever the card measures. That is what lets one rule trace every card at
   the same speed with nothing measured in JavaScript. */
.photofx {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  pointer-events: none;          /* whatever is underneath keeps the click */
  overflow: visible;

  /* How far in from the photograph's edge the frame sits.

     One length for all four sides, which is the whole reason this is a clamp
     on `vw` and not a percentage. A percentage inset would resolve against
     WIDTH horizontally and HEIGHT vertically, and these cards are 3:2 — so
     the same "3%" would be 19px down the sides and 13px along the top, which
     is not a frame, it is a mistake.

     It tracks the card because the card tracks the viewport: at 390px the
     card is full width (~350px) and this floors at 12px = 3.4% of it; at
     1280px the card is ~650px and this ceilings at 24px = 3.7% of it. So the
     margin stays visually constant across the range without needing a
     container query — and without a fixed pixel value that would look mean on
     a wide card and heavy on a phone. */
  --trace-inset: clamp(0.35rem, 1vw, 1rem);

  /* WHERE THE TWO SEGMENTS START.

     A <rect>'s path begins at its top-left corner and runs clockwise, so
     TL sits at 0 and BR at 50 — those two are free, which is why the first
     version of this used them. Bottom-left and top-right are not free: their
     positions along the perimeter depend on the card's shape.

       r = width / height
       TR = 50r / (r + 1)          BL = 100 - 50 / (r + 1)

     For 3:2 that is 30 and 80; for 4:3, 28.57 and 78.57. Opposite corners are
     always exactly 50 apart on a rectangle, so each segment still covers
     precisely half the perimeter whatever the shape.

     A host with a different picture shape sets --trace-ar and the corners
     follow. Defaulting to 1.5 means a host that forgets is slightly off, not
     broken. */
  --trace-ar: 1.5;
  --trace-tr: calc(50 * var(--trace-ar) / (var(--trace-ar) + 1));
  --trace-bl: calc(100 - 50 / (var(--trace-ar) + 1));
}
/* The teaser tiles are the one 4:3 host; everything else is 3:2. */
.teaser__media .photofx { --trace-ar: 1.3333; }

/* ...and where container queries exist, from the CARD rather than the
   viewport. The `vw` clamp above was tuned for the wide room cards and holds
   ~3.6% of their width — but the same number is ~6% of a three-up pillar and
   would be ~8% of a four-up teaser tile, because those cards do not track the
   viewport at the same rate (the pillars go from three columns to one at
   60rem, so their width jumps). `cqw` is a percentage of THIS card, so one
   figure is right for every host at every breakpoint. The `vw` clamp stays as
   the fallback, which is exactly what shipped before. */
@supports (container-type: inline-size) {
  .room__media, .pillar__figure, .teaser__media { container-type: inline-size; }
  .photofx { --trace-inset: clamp(0.35rem, 1.8cqw, 1rem); }
}

/* The dim. Soft, and doing real work: it is what lets the frame be WHITE.
   White was rejected first time round because it disappeared into bright
   concrete and pale walls, which is most of what these photographs contain —
   with a fifth of the light taken out underneath it, it reads everywhere.
   Same ink as the gallery's own veil, one step lighter, because that one is
   seating a solid disc and this is only seating a hairline. */
.photofx__tint {
  /* Deliberately NO rx/ry, here or in the markup. The wrapper is
     `overflow: hidden` and carries the photograph's own corner, so the tint is
     clipped to exactly the image's shape and cannot drift from it again. */
  fill: rgba(33, 29, 24, 0.20);
  opacity: 0;
  transition: opacity var(--t-hover) var(--ease);
}

.photofx__line {
  /* SVG2 geometry properties. The element also carries x/y/width/height as
     attributes, so a browser that ignores these still gets a full-bleed
     outline rather than a zero-sized invisible one. */
  x: var(--trace-inset);
  y: var(--trace-inset);
  width: calc(100% - var(--trace-inset) * 2);
  height: calc(100% - var(--trace-inset) * 2);

  /* Square, like the photograph it frames. This used to derive a radius from
     the card's own, floored at 4px so a hard corner would not sit inside a
     rounded one; with the picture itself square there is nothing left to
     match and the frame is simply square too. */
  rx: var(--radius);
  ry: var(--radius);
  fill: none;
  stroke: #FFFFFF;
  stroke-width: 1.5px;
  stroke-linejoin: round;
  /* The dash PERIOD is the whole path (50 + 50 = 100 = pathLength), and that
     is load-bearing rather than tidy. Segment A runs from the bottom-left
     corner up and over the top, which crosses the point where the path ends
     and begins again; with a period longer than the path the wrapped part is
     simply not drawn and the segment stops dead at the top-left corner.
     Matching the period to the path length makes the pattern continue around
     the seam, so the segment arrives at the top-right corner as it should.

     Because the period now equals the path, an offset alone can never hide
     the line — sliding it just moves which half is lit. So the growth is in
     the dash ARRAY: `0 100` is a zero-length segment, `50 50` is half the
     perimeter, and the interpolation between them keeps the period at 100 the
     whole way. The offsets below only decide WHERE each one starts. */
  stroke-dasharray: 0 100;
  opacity: 0;
  /* Leaving: the line retracts the way every other hover on this site
     reverses, but the opacity fade is much shorter than the retraction, so
     what the eye actually sees is a clean fade rather than the line being
     sucked backwards into its corner. The tint fades on the same curve and
     the same duration, so the two leave as one thing. */
  /* The duration lives in --t-trace and nowhere else, so all three hosts —
     room cards, why-pillars, teaser tiles — change together. Each segment
     covers half the perimeter, which is why the token reads faster than the
     apparent pen speed suggests. */
  /* LINEAR, and this is the fix that two rounds of "make it slower" missed.
     --ease-out is cubic-bezier(0.23, 1, 0.32, 1): it puts 42% of the travel
     in the first 150ms and 83% in the first 400ms. Against that curve,
     doubling the duration changes almost nothing a viewer can see — the line
     still bolts out of the corner and then creeps the last few percent, which
     is exactly what "super gyors" describes. Every other motion here wants
     that curve, because everything else is a thing ARRIVING and should settle.
     This one is a pen drawing a line, and a pen moves at one speed. */
  transition: stroke-dasharray var(--t-trace) linear,
              opacity var(--t-hover) var(--ease);
}
/* A negative offset moves the pattern's start forward along the path, so
   `-BL` puts segment A's origin on the bottom-left corner and `-TR` puts
   segment B's on the top-right. Both then grow CLOCKWISE, which sends A up
   the left edge and across the top, and B down the right edge and across the
   bottom. Each finishes exactly where the other began. */
.photofx__line--a { stroke-dashoffset: calc(0 - var(--trace-bl)); }
.photofx__line--b { stroke-dashoffset: calc(0 - var(--trace-tr)); }

/* The zoom is part of this component too, so all three hosts get the same
   restrained 1.03 from one place — the room cards and teaser tiles each had
   their own copy of it, and the pillars had none at all. */
.fx-photo img { transition: transform 600ms var(--ease); }

/* Gated on a real pointer, exactly like the card treatments around it. On
   touch the rest state is the ONLY state, so a tap cannot leave a card
   wearing half a border or a stuck grey wash. */
@media (hover: hover) and (pointer: fine) {
  .fx-photo:hover img { transform: scale(1.03); }
  .fx-photo:hover .photofx__tint { opacity: 1; }
  .fx-photo:hover .photofx__line {
    stroke-dasharray: 50 50;
    opacity: 1;
    /* Arriving: the ink appears at once and the line does the travelling. The
       tint arrives on the same short curve, so the dim and the first stroke
       land together instead of one trailing the other. */
    transition: stroke-dasharray var(--t-trace) linear,
                opacity var(--t-press) var(--ease);
  }
  .fx-photo:hover .photofx__tint { transition: opacity var(--t-press) var(--ease); }
}

/* Asked for less motion: the outline is already closed, so hovering fades a
   complete frame and its dim in instead of drawing one. Nothing travels. (The
   global reduce rule drops stroke-dashoffset from every transition anyway;
   this makes the resting state the finished shape so that removal is
   invisible rather than leaving the frame permanently hidden.) */
@media (prefers-reduced-motion: reduce) {
  .photofx__line { stroke-dasharray: 50 50; }
  /* The zoom is switched OFF, not merely un-animated. The global reduce rule
     already keeps `transform` out of every transition list, so the scale would
     otherwise arrive as an instant jump — which is more motion, not less. */
  .fx-photo:hover img { transform: none; }
}

/* ---------------------------------------------------------- teaser tiles
   The homepage's way through to the subpages. Four small cards where the full
   room showcase used to be: photograph, title, one enticing line, and a link.
   /szobaink/ still carries the detail — this only has to get a guest there.

   The whole tile is the anchor, so every child is inline-level. It carries
   `fx-photo`, so the dim-and-frame hover is the same object the room cards
   and the pillars use; nothing here restates it. */
.teasers { display: grid; gap: var(--sp-5); grid-template-columns: 1fr; }
@media (min-width: 34rem) { .teasers { grid-template-columns: repeat(2, minmax(0, 1fr)); } }
@media (min-width: 64rem) {
  .teasers { grid-template-columns: repeat(4, minmax(0, 1fr)); gap: var(--sp-4); }
}

.teaser {
  display: flex; flex-direction: column; gap: var(--sp-3);
  text-decoration: none; color: inherit;
}
.teaser__media {
  position: relative; display: block; overflow: hidden;
  border-radius: var(--radius); background: var(--sand-1);
  transition: transform var(--t-hover) var(--ease-out),
              box-shadow var(--t-hover) var(--ease);
}
.teaser__img {
  display: block; width: 100%; aspect-ratio: 4 / 3; object-fit: cover;
}
/* The body takes the leftover height and the link is pushed to the bottom of
   it, so the four "Megnézem" links sit on one line however the copy above them
   wraps. Without this the row reads as four unrelated cards rather than a set:
   the rooms tile runs to three lines and the gallery tile to two, which put
   their links 20px apart. */
.teaser__body { display: flex; flex-direction: column; gap: var(--sp-2); flex: 1 1 auto; }
.teaser__title {
  margin: 0;
  font-size: var(--step-2); line-height: var(--lh-head);
  letter-spacing: var(--ls-head); color: var(--ink);
}
.teaser__text {
  font-size: var(--step--1); line-height: var(--lh-body); color: var(--ink-3);
}
.teaser__cta {
  display: inline-flex; align-items: center; gap: 0.4em;
  align-self: flex-start;
  margin-top: auto; padding-top: var(--sp-2);
  font-size: var(--step--1); font-weight: 600; color: var(--sage-ink);
}
.teaser__cta .icon {
  width: 0.9rem; height: 0.9rem;
  transition: transform var(--t-hover) var(--ease);
}

/* Same gate and the same numbers as every other card on the site. */
@media (hover: hover) and (pointer: fine) {
  .teaser__media:hover { transform: translateY(-3px); box-shadow: var(--lift-2); }
  .teaser:hover .teaser__cta .icon { transform: translateX(3px); }
}

.room__badge {
  position: absolute; top: var(--sp-4); left: var(--sp-4);
  background: rgba(255,255,255,0.94); color: var(--ink);
  font-size: var(--step--2); font-weight: 600; letter-spacing: 0.03em;
  padding: 0.3rem 0.7rem; border-radius: var(--radius);
}
.room__body { display: grid; gap: var(--sp-4); align-content: start; }
.room__title { font-size: var(--step-3); letter-spacing: var(--ls-tight); }
.room__title a { color: inherit; text-decoration: none; }
.room__title a:hover { color: var(--sage-ink); }
.room__desc { color: var(--ink-3); margin: 0; }

.room__tags { display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.tag {
  display: inline-flex; align-items: center; gap: 0.35em;
  font-size: var(--step--2); color: var(--ink-2);
  /* Filled, not outlined. A 1px hairline light enough to stay quiet behind
     six chips per room card cannot also be visible; a pale fill is both. */
  background: var(--sand-1); border-radius: var(--radius);
  padding: 0.25rem 0.65rem;
}
.tag .icon { width: 0.95rem; height: 0.95rem; color: var(--sage-deep); }

.room__foot {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: var(--sp-4); padding-top: var(--sp-4); border-top: 1px solid var(--line);
}
.price { display: flex; align-items: baseline; gap: 0.4em; margin: 0; flex-wrap: wrap; }
/* "from" / "ab" / "od" are labels in front of a number, and read as labels in
   small caps. Hungarian's "-tól" is a case ending on the price itself, sitting
   after it — uppercasing that gives "30 000 Ft TÓL", a word shouting in the
   middle of a phrase. So the small-caps treatment belongs to the prefix
   languages only; :lang() picks it up from <html lang>, no extra class. */
.price__from { font-size: var(--step--2); color: var(--ink-3); letter-spacing: 0.06em; }
.price:not(:lang(hu)) .price__from { text-transform: uppercase; }
.price:lang(hu) .price__from { letter-spacing: normal; margin-inline-start: -0.2em; }
.price__amount { font-family: var(--font-display); font-size: var(--step-2); font-weight: 600; color: var(--ink); line-height: 1; }
.price__unit { font-size: var(--step--1); color: var(--ink-3); }
/* Says which season the figure above was read out of. Takes the whole line so
   the price itself keeps its own baseline row. */
.price__period {
  flex-basis: 100%; font-size: var(--step--2); color: var(--ink-3);
  letter-spacing: 0.02em;
}
.price__period-note { color: var(--ink-4); }
.room__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

/* -------------------------------------------------------------- amenities
   v1 had eight identical emoji cards in a grid — PRODUCT.md's example of a
   templated block. Grouped into named categories and set as plain checked
   lists under a small icon, it becomes information rather than decoration. */
.amenity-groups { display: grid; gap: var(--sp-7); }
@media (min-width: 48rem) { .amenity-groups { grid-template-columns: repeat(2, 1fr); gap: var(--sp-7) var(--sp-8); } }
@media (min-width: 72rem) { .amenity-groups { grid-template-columns: repeat(3, 1fr); } }
.amenity-group { display: grid; gap: var(--sp-4); align-content: start; border-top: 1px solid var(--line); padding-top: var(--sp-5); }
.amenity-group__head { display: flex; align-items: center; gap: 0.6em; }
.amenity-group__head .icon { width: 1.25rem; height: 1.25rem; color: var(--sage-deep); }
.amenity-group__title { font-size: var(--step-1); font-weight: 600; }
.amenity-list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
.amenity-list li { display: flex; gap: 0.6em; align-items: baseline; font-size: var(--step-0); color: var(--ink-2); }
/* --sage-deep, not --sage: these ticks mean "included in the price", and
   the pastel measured 1.82:1 on the sand tint — invisible at a glance. */
.amenity-list .icon { width: 0.9rem; height: 0.9rem; color: var(--sage-deep); flex: none; transform: translateY(0.15em); }
.amenity-list__sub { display: block; color: var(--ink-3); font-size: var(--step--1); }

/* The one item in the list that is a reason to book rather than an inventory
   line. It stays a list item — same row, same rhythm, no rule, no box, no
   colour — and is lifted by exactly two things: full-strength ink instead of
   --ink-2, and its own glyph at the weight of the surrounding ticks. Enough
   that the eye stops on it once while scanning seven amenities; not enough to
   read as an advertisement inside a specification list. */
.amenity-list__item--lead { color: var(--ink); font-weight: 500; }
.amenity-list__item--lead .icon { width: 1rem; height: 1rem; }
/* "on request", "please tell us first". A qualifier, not a second line of
   description, so it stays on the item's own line and reads as part of it. */
.amenity-list__note {
  color: var(--ink-3); font-size: var(--step--1);
}
.amenity-list__note::before { content: " · "; color: var(--ink-4); }
/* Which units a run of items actually applies to. Sentence case, sitting on
   the group's own ground — the alternative was eight repeated chips saying
   "apartments only", which is louder and says less. */
.amenity-list__scope {
  margin: var(--sp-4) 0 var(--sp-3);
  font-size: var(--step--1);
  font-weight: 600;
  color: var(--ink-2);
}
.amenity-group > .amenity-list__scope:first-of-type { margin-top: var(--sp-2); }

/* -------------------------------------------------------------------- CTA
   The recurring invitation. Deliberately quiet: a hairline-bounded row with
   a sentence and two actions. It appears after rooms, after the gallery,
   after the reviews and near the FAQ — frequent, but never a banner. */
.cta-strip {
  display: grid; gap: var(--sp-5); align-items: center;
  padding: clamp(2rem, 4vw, 3rem) 0;
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
@media (min-width: 56rem) {
  .cta-strip { grid-template-columns: minmax(0, 1fr) auto; gap: var(--sp-8); }
}
.cta-strip__title { font-size: var(--step-2); margin: 0 0 var(--sp-2); }
.cta-strip__text { margin: 0; color: var(--ink-3); max-width: 54ch; font-size: var(--step-0); }
.cta-strip__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); }

/* A larger, centred version for the end of a page. Sand tint, never dark. */
.cta-panel { background: var(--sand-2); padding-block: var(--section-y); text-align: center; }
.cta-panel__title { font-size: var(--step-3); letter-spacing: var(--ls-tight); margin: 0 auto; max-width: 22ch; }
.cta-panel__text { margin: var(--sp-4) auto 0; max-width: 54ch; color: var(--ink-2); }
.cta-panel__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: center; margin-top: var(--sp-6); }
.cta-panel .btn--ghost { border-color: var(--line-strong); background: rgba(255,255,255,0.6); }
.cta-panel .btn--ghost:hover { background: var(--white); }

/* ----------------------------------------------------------------- gallery
   Photography at full quality and generous size. The editorial variant gives
   the lead image two cells so the grid is never a uniform tile sheet. */
.gallery { display: grid; grid-template-columns: repeat(2, 1fr); gap: var(--sp-3); }
@media (min-width: 56rem) { .gallery { grid-template-columns: repeat(3, 1fr); gap: var(--sp-4); } }
.gallery--editorial > .shot:first-child { grid-column: span 2; grid-row: span 2; }
@media (max-width: 55.99rem) {
  .gallery--editorial > .shot:first-child { grid-column: span 2; grid-row: span 1; }
}

.shot {
  position: relative; padding: 0; border: 0; background: var(--sand-1);
  border-radius: var(--radius); overflow: hidden; display: block;
  transition: transform 400ms var(--ease);
}
.shot img {
  width: 100%; height: 100%; object-fit: cover; aspect-ratio: 4 / 3;
  transition: transform var(--t-panel) var(--ease-out),
              filter var(--t-hover) var(--ease);
}
@media (hover: hover) and (pointer: fine) {
  /* A tile is one of 57. The response has to be quiet enough to survive being
     repeated across a whole grid as the cursor crosses it.
     The brightening this rule used to carry is gone: the veil below now does
     the opposite, and a tile cannot get lighter and darker at the same time. */
  .shot:hover img { transform: scale(1.04); }
}
.shot:active img { transform: scale(1.01); }

/* ------------------------------------------------------------- zoom mark
   The cue that says a thumbnail opens larger. A gentle veil rather than the
   heavy scrim the pattern usually ships with — this palette is a pale sand
   and a 50% black wash over it reads as the photograph having failed to
   load. 0.22 of the ink colour is enough to seat a white disc on top of any
   of these photographs and no more than that.

   The disc is the same object as the scroll-to-top button: same ground, same
   blur, same lift. It is the site's one floating-circular-control shape, and
   inventing a second one for the gallery would be a widget rather than a part
   of the site.

   Both halves are pointer-transparent, so the whole tile stays one click
   target and the icon is never the only way in. */
.shot__veil {
  position: absolute; inset: 0; pointer-events: none;
  background: rgba(33, 29, 24, 0.22);
  opacity: 0;
  transition: opacity var(--t-hover) var(--ease);
}
.shot__zoom {
  position: absolute; top: 50%; left: 50%;
  width: 2.75rem; height: 2.75rem; margin: -1.375rem 0 0 -1.375rem;
  border-radius: 50%; pointer-events: none;
  background: rgba(255, 254, 252, 0.94);
  backdrop-filter: blur(8px);
  color: var(--ink);
  display: grid; place-items: center;
  box-shadow: var(--lift-1);
  opacity: 0; transform: scale(0.86);
  /* --t-pop is the token for "small things arriving", which is what this is.
     Under prefers-reduced-motion the global rule drops transform from the
     transition list, so the disc still fades but no longer grows. */
  transition: opacity var(--t-pop) var(--ease-out),
              transform var(--t-pop) var(--ease-out);
}
.shot__zoom-icon { width: 1.125rem; height: 1.125rem; }

/* A videócsempe lejátszásjele. Mindig látszik — nem hoverre —, mert nem
   díszítés, hanem az egyetlen dolog, ami megmondja, hogy ez a csempe mozogni
   fog, ha rákattintasz. A nagyító-korong hoverre attól még megjelenik fölötte;
   a kettő nem ütközik, mert ez a sarokban ül, az meg középen.
   Tömör háromszög, saját alakzatként: egy ikon a sprite-ból új build nélkül
   nem kerülne bele, a feltöltött videó viszont build nélkül jelenik meg. */
.shot__play {
  position: absolute; left: 0.5rem; bottom: 0.5rem;
  width: 1.75rem; height: 1.75rem; border-radius: 50%;
  background: rgba(255, 254, 252, 0.94);
  box-shadow: var(--lift-1);
  pointer-events: none;
}
.shot__play::after {
  content: ""; position: absolute; top: 50%; left: 50%;
  width: 0; height: 0;
  transform: translate(-38%, -50%);
  border-left: 0.5rem solid var(--ink);
  border-top: 0.32rem solid transparent;
  border-bottom: 0.32rem solid transparent;
}

@media (hover: hover) and (pointer: fine) {
  .shot:hover .shot__veil { opacity: 1; }
  .shot:hover .shot__zoom { opacity: 1; transform: none; }
}
/* Keyboard focus gets the same treatment on every device, hover or not: the
   mark is how a tile says what it does, and a guest tabbing the grid needs
   that as much as one with a mouse. Outside the hover query on purpose. */
.shot:focus-visible .shot__veil { opacity: 1; }
.shot:focus-visible .shot__zoom { opacity: 1; transform: none; }

/* Touch has no hover state to reveal anything with, so the mark is simply
   there — but not as the same object. Centred and 44px it was fine on one
   tile and wrong on a grid: twenty discs down the middle of a phone screen
   annotate the gallery rather than sit in it, and the veil behind them would
   be dimming every photograph permanently. So on touch it becomes a small
   badge in the corner of each tile — still the same glyph, still says the
   picture opens, no longer the first thing the eye lands on.

   Pure media-query state with nothing scripted behind it, so there is no way
   for a tile to be left stuck half-open after a tap — the sticky-:hover
   problem that usually breaks this pattern on touch cannot arise, because the
   hover rules above are behind `(hover: hover) and (pointer: fine)` and are
   never evaluated here. */
@media (hover: none) {
  .shot__veil { display: none; }
  .shot__zoom {
    top: auto; left: auto; bottom: var(--sp-2); right: var(--sp-2); margin: 0;
    width: 1.75rem; height: 1.75rem;
    background: rgba(255, 254, 252, 0.82);
    box-shadow: 0 1px 3px rgba(33, 29, 24, 0.22);
    opacity: 1; transform: none; transition: none;
  }
  .shot__zoom-icon { width: 0.875rem; height: 0.875rem; }
  /* The press still answers, since that is now the only feedback there is. */
  .shot:active .shot__zoom { transform: scale(0.92); }
}
.gallery--editorial > .shot:first-child img { aspect-ratio: 1 / 1; }
@media (max-width: 55.99rem) { .gallery--editorial > .shot:first-child img { aspect-ratio: 16 / 10; } }
.shot::after {
  content: ""; position: absolute; inset: 0; pointer-events: none;
  box-shadow: inset 0 0 0 1px rgba(33,29,24,0.06); border-radius: var(--radius);
}

.filters { display: flex; flex-wrap: wrap; gap: var(--sp-2); margin-bottom: var(--sp-5); }
.filters__btn {
  border: 1px solid var(--line-strong); background: transparent; color: var(--ink-2);
  border-radius: var(--radius); padding: 0.4rem 0.95rem;
  font-size: var(--step--1); font-weight: 500;
  transition: background-color var(--dur) var(--ease), border-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.filters__btn:hover { border-color: var(--sage-deep); background: var(--sand-1); }
.filters__btn[aria-pressed="true"] { background: var(--sage-deep); border-color: var(--sage-deep); color: var(--white); }

/* --------------------------------------------------------------- lightbox */
.lightbox {
  position: fixed; inset: 0; z-index: 120;
  display: grid; place-items: center;
  background: rgba(28,24,20,0.86);
  opacity: 0; visibility: hidden;
  transition: opacity var(--t-panel) var(--ease),
              visibility var(--t-panel);
}
.lightbox[data-open="true"] { opacity: 1; visibility: visible; }
.lightbox__img {
  max-width: min(94vw, 1400px); max-height: 86svh;
  width: auto; height: auto; object-fit: contain;
  border-radius: var(--radius); box-shadow: var(--lift-3);
  /* 0.94, never 0: nothing in the world appears out of nothing, and an image
     that grows from a point reads as a trick rather than as a photograph
     being brought closer. */
  transform: scale(0.94);
  transition: transform var(--t-panel) var(--ease-out),
              opacity var(--t-pop) var(--ease);
}
.lightbox[data-open="true"] .lightbox__img { transform: scale(1); }

/* A videó ugyanazt a keretet kapja, mint a fotó: azonos maximumok, azonos
   lekerekítés és árnyék, hogy a lightbox egy nézőnek látsszon, ne kettőnek.
   A skálázó belépőt viszont NEM kapja meg — a beépített lejátszóvezérlők egy
   transformált elemben elmosódnak, és a gomb, amit a vendég meg akar nyomni,
   fontosabb, mint a mozdulat. */
.lightbox__video {
  max-width: min(94vw, 1400px); max-height: 86svh;
  width: auto; height: auto;
  border-radius: var(--radius); box-shadow: var(--lift-3);
  background: #000;
}
.lightbox__video[hidden] { display: none; }
/* Paging. The new photograph arrives from the side the guest asked for, so
   the direction of travel matches the button they pressed. A transition, not
   a keyframe, so hammering the arrow key retargets instead of restarting. */
.lightbox[data-swap="next"] .lightbox__img { opacity: 0; transform: scale(1) translateX(-2.5%); }
.lightbox[data-swap="prev"] .lightbox__img { opacity: 0; transform: scale(1) translateX(2.5%); }
.lightbox__btn {
  transition: background-color var(--t-hover) var(--ease),
              transform var(--t-press) var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .lightbox__btn:hover { background: var(--white); }
}
.lightbox__btn:active { transform: translateY(-50%) scale(0.94); }
.lightbox__close:active { transform: scale(0.94); }
.lightbox__btn {
  position: absolute; top: 50%; transform: translateY(-50%);
  width: 2.75rem; height: 2.75rem; border: 0; border-radius: 50%;
  background: rgba(255,255,255,0.92); color: var(--ink);
  display: grid; place-items: center;
}
.lightbox__btn--prev { left: clamp(0.5rem, 2vw, 1.5rem); }
.lightbox__btn--next { right: clamp(0.5rem, 2vw, 1.5rem); }
.lightbox__close { position: absolute; top: clamp(0.5rem, 2vw, 1.5rem); right: clamp(0.5rem, 2vw, 1.5rem); transform: none; }
.lightbox__count { position: absolute; bottom: clamp(0.75rem, 2vw, 1.5rem); left: 50%; transform: translateX(-50%);
  color: rgba(255,255,255,0.86); font-size: var(--step--1); font-variant-numeric: tabular-nums; }

/* ----------------------------------------------------------------- reviews
   Set as pull quotes on hairlines rather than as shadowed cards, so six of
   them read as a column of voices instead of a testimonial widget. */
.reviews { display: grid; gap: var(--sp-6); }
@media (min-width: 56rem) { .reviews { grid-template-columns: repeat(3, 1fr); gap: var(--sp-7); } }

/* The full list flows in columns instead of sitting in grid rows, and that is
   a consequence of the quotes being real. A grid gives every card in a row the
   height of the tallest one: with eight genuine reviews running from 27 words
   to 55, that left roughly 200px of dead space under the short ones, and eight
   cards in a three-column grid also ends on a row of two with a visible hole
   where the ninth would be — the same incomplete-trailing-row problem the
   gallery preview had.

   Multi-column balances by HEIGHT rather than by count, so the ragged lengths
   stop being a defect and the short last column stops reading as a gap. No
   "show more" for the same reason: this is the reviews page, all eight belong
   on it, and nothing here is crowded once the heights balance. */
@media (min-width: 56rem) {
  .reviews--flow {
    display: block;
    columns: 3;
    column-gap: var(--sp-7);
  }
  .reviews--flow .review {
    /* A quote split across a column break is unreadable, and the footer
       landing in a different column from its own words is worse. */
    break-inside: avoid;
    margin-bottom: var(--sp-7);
  }
}
.review {
  display: grid; gap: var(--sp-4); align-content: start;
  border-top: 1px solid var(--line); padding-top: var(--sp-5);
  transition: border-color var(--t-hover) var(--ease);
}
/* A review is not a control. The rule above it warms, and nothing moves —
   lifting a paragraph the guest is in the middle of reading is worse than
   doing nothing at all. */
@media (hover: hover) and (pointer: fine) {
  .review:hover { border-top-color: var(--sage-rule); }
}
.review__mark { color: var(--sage-deep); display: flex; }
.review__mark .icon { width: 1.5rem; height: 1.5rem; }
.review__stars { display: flex; gap: 0.12rem; margin: 0; color: var(--sage-deep); }
.review__stars .icon { width: 0.95rem; height: 0.95rem; fill: currentColor; }
/* An unfilled star is half the rating: without it a four-star review reads as
   four stars, not as four out of five.

   Both weights are FILLED paths — #i-star-fill is a solid star, #i-star is
   Phosphor's outline weight, which draws its ring as a filled shape too. So
   the empty position keeps `fill: currentColor` and only changes colour. It
   used to say `fill: none`, which on a fill-based path rendered nothing at
   all: the fifth star vanished and took the "out of five" frame with it. */
.review__stars .icon[data-off="true"] { color: var(--line-strong); }
.review__text { margin: 0; color: var(--ink-2); font-size: var(--step-0); text-wrap: pretty; }
.review__foot { display: flex; align-items: center; gap: var(--sp-3); margin-top: auto; }
.review__avatar {
  width: 2.25rem; height: 2.25rem; border-radius: 50%; flex: none;
  display: grid; place-items: center;
  background: var(--sand-1); color: var(--sage-ink);
  font-size: var(--step--2); font-weight: 700; letter-spacing: 0.02em;
}
.review__name { font-weight: 600; color: var(--ink); font-size: var(--step--1); }
.review__meta { font-size: var(--step--2); color: var(--ink-3); }

/* --------------------------------------------------------------- discounts */
.tiers { display: grid; gap: var(--sp-5); }
/* Column count follows the number of tiers, it is not fixed at three. When the
   third tier was dropped, `repeat(3, 1fr)` left the two remaining cards in the
   left two thirds with an empty column beside them. auto-fit also means adding
   a tier back in discounts.json needs no CSS change. */
@media (min-width: 48rem) {
  .tiers { grid-template-columns: repeat(auto-fit, minmax(15rem, 1fr)); gap: var(--sp-6); }
}
.tier {
  display: grid; gap: var(--sp-2); align-content: start;
  background: var(--white); border: 1px solid var(--sand-3);
  border-radius: var(--radius); padding: var(--sp-6);
  transition: transform var(--t-hover) var(--ease-out),
              box-shadow var(--t-hover) var(--ease),
              border-color var(--t-hover) var(--ease);
}
/* Deliberately the same numbers as the room card. Two card families that lift
   by different amounts read as two different sites. */
@media (hover: hover) and (pointer: fine) {
  .tier:hover { transform: translateY(-3px); box-shadow: var(--lift-2); border-color: var(--sage-rule); }
}
.tier__percent {
  font-family: var(--font-display); font-size: clamp(1.75rem, 3vw, 2.25rem);
  font-weight: 600; color: var(--sage-ink); margin: 0; line-height: 1;
  display: flex; align-items: center; gap: 0.32em;
}
.tier__title { font-size: var(--step-1); }
.tier__body { margin: 0; color: var(--ink-3); font-size: var(--step--1); }
.editor-note {
  display: flex; gap: 0.6em; align-items: flex-start;
  margin-top: var(--sp-5); padding: var(--sp-4) var(--sp-5);
  background: var(--blue-1); border: 1px solid var(--blue-3);
  border-radius: var(--radius); color: var(--ink-2); font-size: var(--step--1);
}
.editor-note .icon { color: var(--focus); flex: none; transform: translateY(0.15em); }

/* ------------------------------------------------------------------ about */
.about { display: grid; gap: var(--sp-6); align-items: center; }
@media (min-width: 60rem) { .about { grid-template-columns: minmax(0, 1fr) minmax(0, 1.1fr); gap: var(--sp-8); } }
.about__figure { margin: 0; border-radius: var(--radius); overflow: hidden; background: var(--sand-1); }
.about__figure img { width: 100%; aspect-ratio: 4 / 3; object-fit: cover; }
.about__caption { display: flex; align-items: center; gap: 0.45em; margin: var(--sp-3) 0 0; font-size: var(--step--1); color: var(--ink-3); }
.about__caption .icon { color: var(--sage-deep); }

/* --------------------------------------------------------------- location */
.location { display: grid; gap: var(--sp-6); align-items: center; }
@media (min-width: 60rem) { .location { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); gap: var(--sp-8); } }
.map-frame { border-radius: var(--radius); overflow: hidden; border: 1px solid var(--line); background: var(--sand-1); }
.map-frame iframe { display: block; width: 100%; height: clamp(18rem, 42vw, 26rem); border: 0; }

.address-lines { display: grid; gap: var(--sp-4); margin-top: var(--sp-5); }
.address-line { display: flex; gap: 0.75em; align-items: flex-start; }
.address-line .icon { color: var(--sage-deep); flex: none; transform: translateY(0.2em); }
.address-line strong { display: block; font-size: var(--step--2); text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-3); font-weight: 600; }
.address-line span, .address-line a { font-size: var(--step-0); color: var(--ink-2); }
.address-line a { color: var(--sage-ink); }

/* ------------------------------------------------------------------ forms */
.form { display: grid; gap: var(--sp-4); }
.form__legend {
  display: flex; align-items: center; gap: 0.5em;
  margin: var(--sp-4) 0 0; font-size: var(--step--2); font-weight: 600;
  letter-spacing: 0.07em; text-transform: uppercase; color: var(--ink-3);
}
.form__legend:first-child { margin-top: 0; }
.form__legend .icon { color: var(--sage-deep); }
.form__grid { display: grid; gap: var(--sp-4); }
@media (min-width: 34rem) { .form__grid { grid-template-columns: 1fr 1fr; } }

/* align-content: start is load-bearing, not tidiness. These fields sit in a
   two-column grid whose row stretches both to the same height; with auto rows
   and the default alignment, a field with FEWER rows than its partner shares
   the leftover height out among the rows it does have. Szobatípus carries an
   error slot and Vendégek száma did not, so the guest field's two rows grew
   from 21.4/52.8 to 31.7/63.1 and its select sat 10.3px lower than the one
   beside it. Pinning the rows to their content puts the slack at the bottom,
   where it moves nothing. */
.field { display: grid; gap: 0.35rem; align-content: start; }
.field__label { font-size: var(--step--1); font-weight: 500; color: var(--ink-2); }
.field__hint { font-size: var(--step--2); color: var(--ink-3); margin: 0; }
/* The slot is ALWAYS in the layout, empty or not. It used to be
   `:empty { display: none }`, which meant a failed validation inserted a line
   of text and a grid gap under the field and shoved everything below it down:
   measured, six simultaneous errors moved the submit button 139px and grew the
   form by the same. Reserving one line costs ~20px per field once, and costs
   it whether or not anyone ever sees an error — which is the trade, and it is
   the right way round, because the jump happened at the exact moment the guest
   was being asked to re-read the form.

   min-height rather than a fixed height: if a message ever does wrap, it grows
   instead of overlapping the next field. That degrades to a small shift rather
   than to unreadable text. The messages themselves are kept to one line at the
   narrowest field width (300px at a 390px viewport) so it does not arise. */
.field__error {
  margin: 0; font-size: var(--step--2); color: #9A3412;
  line-height: 1.25; min-height: 1.25em;
}

.input, .select, .textarea {
  width: 100%;
  background: var(--white);
  border: 1px solid var(--line-strong);
  border-radius: var(--radius);
  padding: 0.7rem 0.85rem;
  font-size: var(--step-0);
  color: var(--ink);
  transition: border-color var(--t-hover) var(--ease),
              box-shadow var(--t-hover) var(--ease),
              background-color var(--t-hover) var(--ease);
}
/* --ink-4 is 3.12:1 — a graphic threshold, not a text one. Placeholders
   are text. */
.input::placeholder, .textarea::placeholder { color: var(--ink-3); }
.input:hover, .select:hover, .textarea:hover { border-color: var(--sage-deep); }
.input:focus, .select:focus, .textarea:focus {
  outline: none; border-color: var(--sage-deep);
  /* The ring grows from 0 rather than appearing at full size, which is what
     makes focus feel like the field waking up instead of a border swapping. */
  box-shadow: 0 0 0 3px rgba(62, 115, 64, 0.18);
}
.input:not(:focus), .select:not(:focus), .textarea:not(:focus) {
  box-shadow: 0 0 0 0 rgba(62, 115, 64, 0);
}
.input[aria-invalid="true"], .select[aria-invalid="true"], .textarea[aria-invalid="true"] { border-color: #9A3412; }
/* height, not min-height: these two must be exactly equal, not merely at
   least as tall. The textarea below is deliberately exempt — it is a
   multi-line control and sizes to its own rows. */
.input, .select { height: var(--control-h); }
.textarea { min-height: 7.5rem; resize: vertical; }

/* ------------------------------------------------------------- date fields
   An empty <input type="date"> paints the locale's own format tokens —
   "éééé. hh. nn." in Hungarian — and there is no `placeholder` attribute on a
   date input to replace them with. So the words come from a sibling span and
   the mask is hidden underneath it while the field is empty.

   `color: transparent` on the whole control rather than
   ::-webkit-datetime-edit: that pseudo-element does not exist in Firefox,
   which paints the same mask and would have been left showing it. The picker
   indicator is given its own colour back so the affordance survives.

   Everything here is behind `.js`. site.js sets that class on <html> before
   first paint, and it is what makes the fallback honest: with scripting off
   `data-empty` would never be cleared, the field would stay transparent, and
   the guest could not read the date they had just picked. Then the mask
   simply shows, exactly as it does today. */
.datefield { position: relative; display: block; }
.datefield .input { position: relative; z-index: 1; background: transparent; }
.datefield__ph {
  position: absolute; left: 0.85rem; top: 50%; transform: translateY(-50%);
  color: var(--ink-3); font-size: var(--step-0);
  pointer-events: none; opacity: 0; z-index: 2;
  transition: opacity var(--t-hover) var(--ease);
}
.js .datefield .input[data-empty="true"]:not(:focus) { color: transparent; }
.js .datefield .input[data-empty="true"]:not(:focus)::-webkit-calendar-picker-indicator {
  color: var(--ink-3);
}
.js .datefield .input[data-empty="true"]:not(:focus) ~ .datefield__ph { opacity: 1; }
/* The white ground belongs to the field, but the input above it is
   transparent so the placeholder can sit behind its text layer. */
.datefield::before {
  content: ""; position: absolute; inset: 0; z-index: 0;
  background: var(--white); border-radius: var(--radius);
}
.select {
  appearance: none;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 16 16' fill='none' stroke='%236E6860' stroke-width='1.5'%3E%3Cpath d='M4 6l4 4 4-4'/%3E%3C/svg%3E");
  background-repeat: no-repeat; background-position: right 0.75rem center; background-size: 1rem;
  padding-right: 2.25rem;
}

.nights { margin: 0; font-size: var(--step--1); color: var(--sage-ink); font-weight: 500; }
.nights:empty { display: none; }
/* Names the long-stay rule the chosen dates already earn. */
.promo-hint {
  margin: 0; font-size: var(--step--1); color: var(--ink-2); font-weight: 500;
  padding: var(--sp-3) var(--sp-4);
  background: var(--sand-1); border-left: 3px solid var(--sage-rule);
  border-radius: 0 var(--radius) var(--radius) 0;
}
/* Same reserve as .field__error, and for the same reason: this line sits
   directly above the submit button, so appearing on a failed submit pushed
   the button 37.5px down — out from under the pointer that had just pressed
   it. The slot is held open whether or not it has anything to say.
   The popup newsletter form keeps its own :empty rule: it is a small panel
   with no button below the status line, so nothing there can be pushed. */
/* line-height is pinned as well as min-height, and they are the same
   number on purpose: the reserve has to equal the line box the text
   will actually occupy. Left to inherit --lh-body (1.65) the filled
   line measured 21.4px against an 18.2px reserve, and the button still
   moved 3.3px. */
.form-status { margin: 0; font-size: var(--step--1);
               line-height: 1.4; min-height: 1.4em; }
.form-status[data-state="ok"]    { color: var(--sage-ink); }
.form-status[data-state="error"] { color: #9A3412; }

.check { display: flex; gap: 0.6em; align-items: flex-start; font-size: var(--step--1); color: var(--ink-3); }
.check input { margin-top: 0.25em; accent-color: var(--sage-deep); width: 1rem; height: 1rem; flex: none; }

.contact { display: grid; gap: var(--sp-7); align-items: start; }
@media (min-width: 60rem) { .contact { grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr); gap: var(--sp-8); } }
.form-card { background: var(--white); border: 1px solid var(--line); border-radius: var(--radius); padding: clamp(1.5rem, 3vw, 2.25rem); }

/* ------------------------------------------------------------- newsletter */
.newsletter { display: grid; gap: var(--sp-6); align-items: center; }
@media (min-width: 56rem) { .newsletter { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); gap: var(--sp-8); } }
.newsletter .sec-head { border-top: 0; padding-top: 0; margin-bottom: 0; grid-template-columns: 1fr; }
.newsletter__form { display: grid; gap: var(--sp-3); }
.newsletter__row { display: grid; gap: var(--sp-3); }
@media (min-width: 34rem) { .newsletter__row { grid-template-columns: 1fr auto; } }

/* -------------------------------------------------------------------- FAQ */
.faq { display: grid; gap: var(--sp-5); align-items: start; }
@media (min-width: 60rem) { .faq { grid-template-columns: 1fr 1fr; gap: var(--sp-5) var(--sp-7); } }
.faq__col { display: grid; gap: var(--sp-3); align-content: start; }
.qa { border-bottom: 1px solid var(--line); }
.qa__q {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-4);
  padding: var(--sp-4) 0; cursor: pointer; list-style: none;
  font-family: var(--font-display); font-size: var(--step-1); font-weight: 500;
  color: var(--ink); letter-spacing: var(--ls-head);
}
.qa__q::-webkit-details-marker { display: none; }
/* Matches the collapse duration, so the caret and the panel finish together
   instead of the mark hanging on after the box has closed. The panel's own
   duration is scaled by the distance it travels (90-220ms, site.js), so this
   cannot be a fixed number and stay true — site.js writes the figure it just
   used into --qa-dur on the <details>. The 200ms fallback is what a browser
   without the script, or before the first toggle, gets. */
.qa__sign {
  color: var(--sage-deep); flex: none; display: flex;
  transition: transform var(--qa-dur, 200ms) var(--ease);
}
.qa[open] .qa__sign { transform: rotate(180deg); }
/* The animated box: no padding, no border, nothing but height. `overflow`
   clips the answer as the box shrinks — without it the text sits at full size
   inside a collapsing frame and the close reads as a freeze then a snap.
   `contain` keeps the per-frame layout work inside this subtree. */
.qa__a { overflow: hidden; contain: layout paint; color: var(--ink-3); font-size: var(--step-0); }
.qa__inner { padding-bottom: var(--sp-5); }
.qa__inner > :first-child { margin-top: 0; }
@media (prefers-reduced-motion: reduce) {
  .qa__sign { transition: none; }
}

/* ------------------------------------------------------- conditions page */
.quickfacts { display: grid; gap: var(--sp-4); }
@media (min-width: 40rem) { .quickfacts { grid-template-columns: repeat(2, 1fr); } }
/* auto-fit rather than a hard `repeat(5, 1fr)`: this row is five facts on the
   conditions page, six now that the pet policy has joined them, and four in
   the teaser on the booking and FAQ pages. A fixed column count had to be
   wrong for two of those three — five tracks with four facts left a visible
   hole, and a sixth fact fell out of the row entirely. */
@media (min-width: 64rem) {
  .quickfacts { grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr)); gap: var(--sp-5); }
}
.quickfact { display: flex; gap: 0.7em; align-items: flex-start; border-top: 1px solid var(--line); padding-top: var(--sp-4); }
.quickfact .icon { color: var(--sage-deep); flex: none; transform: translateY(0.15em); }
.quickfact__label { display: block; font-size: var(--step--2); text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-3); font-weight: 600; }
.quickfact__value { font-size: var(--step-0); color: var(--ink); }

.datalist { display: grid; }
.datarow { display: grid; gap: var(--sp-1) var(--sp-6); padding: var(--sp-4) 0; border-bottom: 1px solid var(--line); }
@media (min-width: 48rem) { .datarow { grid-template-columns: minmax(0, 0.5fr) minmax(0, 1fr); } }
.datarow__label { font-weight: 600; color: var(--ink); font-size: var(--step-0); }
.datarow__value { color: var(--ink-2); }
.datarow__note { display: block; font-size: var(--step--1); color: var(--ink-3); margin-top: var(--sp-1); }

.callout {
  display: flex; gap: 0.7em; align-items: flex-start;
  background: var(--sand-1); border: 1px solid var(--sand-3);
  border-radius: var(--radius); padding: var(--sp-4) var(--sp-5); color: var(--ink-2);
  font-size: var(--step--1);
}
.callout .icon { color: var(--sage-deep); flex: none; transform: translateY(0.15em); }
.callout p { margin: 0; }

.paycards { display: grid; gap: var(--sp-4); }
@media (min-width: 40rem) { .paycards { grid-template-columns: repeat(3, 1fr); } }
.paycard { display: grid; gap: var(--sp-2); border: 1px solid var(--line); border-radius: var(--radius); padding: var(--sp-5); background: var(--white); }
.paycard .icon { color: var(--sage-deep); width: 1.35rem; height: 1.35rem; }
.paycard__title { font-weight: 600; color: var(--ink); font-size: var(--step-0); }
.paycard__note { font-size: var(--step--1); color: var(--ink-3); }

.cancel-grid { display: grid; gap: var(--sp-4); }
@media (min-width: 40rem) { .cancel-grid { grid-template-columns: 1fr 1fr; } }
.cancel-card { border: 1px solid var(--sand-3); border-radius: var(--radius); padding: var(--sp-6); background: var(--sand-1); }
.cancel-card__range { margin: 0; font-size: var(--step--1); color: var(--ink-3); text-transform: uppercase; letter-spacing: 0.05em; font-weight: 600; }
.cancel-card__result { font-family: var(--font-display); font-size: var(--step-2); color: var(--ink); margin: var(--sp-2) 0 0; font-weight: 600; }
.cancel-card__note { margin: var(--sp-2) 0 0; font-size: var(--step--1); color: var(--ink-3); }

.pricetable-wrap { overflow-x: auto; border: 1px solid var(--line); border-radius: var(--radius); background: var(--white); }
.pricetable { width: 100%; border-collapse: collapse; min-width: 34rem; font-size: var(--step--1); }
.pricetable th, .pricetable td { padding: var(--sp-3) var(--sp-4); text-align: left; border-bottom: 1px solid var(--line); }
.pricetable thead th { background: var(--sand-1); color: var(--ink); font-weight: 600; font-size: var(--step--2); text-transform: uppercase; letter-spacing: 0.05em; white-space: nowrap; }
.pricetable tbody tr:last-child td { border-bottom: 0; }
.pricetable td:first-child { font-weight: 600; color: var(--ink); white-space: nowrap; }
/* The season the site is quoting from today. Tinted rather than outlined so
   the column reads as one block; the ink stays at full strength so the
   highlight never costs the cells contrast. */
.pricetable .is-now { background: var(--sand-1); color: var(--ink); }
.pricetable thead th.is-now { background: var(--sand-2); box-shadow: inset 0 -3px 0 var(--sage-rule); }
.pricetable td.is-now { font-weight: 600; }
.minstay { display: inline-block; background: var(--sage-1); border: 1px solid var(--sage-3); color: var(--ink-2); border-radius: var(--radius); padding: 0.1rem 0.5rem; font-size: var(--step--2); }

.equip, .tips { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
.equip li, .tips li { display: flex; gap: 0.6em; align-items: baseline; color: var(--ink-2); font-size: var(--step-0); }
.equip .icon, .tips .icon { color: var(--sage-deep); width: 0.9rem; height: 0.9rem; flex: none; transform: translateY(0.15em); }
@media (min-width: 48rem) { .tips { grid-template-columns: 1fr 1fr; gap: var(--sp-3) var(--sp-6); } }

/* -------------------------------------------------------------- room page */
.roomhero { display: grid; gap: var(--sp-6); align-items: center; }
@media (min-width: 60rem) { .roomhero { grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.1fr); gap: var(--sp-8); } }
.roomhero__figure { margin: 0; border-radius: var(--radius); overflow: hidden; background: var(--sand-1); }
.roomhero__figure img { width: 100%; aspect-ratio: 3 / 2; object-fit: cover; }
.roomhero__title { font-size: var(--step-4); letter-spacing: var(--ls-tight); }

/* ------------------------------------------------- élményajánló (guide page)
   Twelve experiences as cards, in two groups.

   Icon-led rather than photo-led, and that is a decision rather than a
   shortcut. Only five of the twelve have a photograph in this library that is
   genuinely of them; the other seven would have been given a picture of
   something else, and the three dated events — the items a visitor most wants
   to look at — have none at all. A grid where five cards carry a photograph
   and seven do not says the seven matter less, which is the wrong thing to
   say. One glyph each, drawn from the set the rest of the site already uses,
   and every card the same weight.

   The whole card is the anchor. Every destination is external, so each one
   ends in the same outbound arrow the site uses elsewhere. */
.xp-grid {
  display: grid; gap: var(--sp-4);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 18rem), 1fr));
}
@media (min-width: 56rem) { .xp-grid { gap: var(--sp-5); } }

.xp {
  display: flex; align-items: flex-start; gap: var(--sp-4);
  padding: var(--sp-5);
  background: var(--white);
  border: 1px solid var(--line);
  border-radius: var(--radius);
  text-decoration: none; color: var(--ink-2);
  transition: transform var(--t-hover) var(--ease-out),
              border-color var(--t-hover) var(--ease),
              box-shadow var(--t-hover) var(--ease);
}
.section--tint .xp { background: var(--white); }

.xp__icon {
  flex: none; width: 2.25rem; height: 2.25rem;
  border-radius: 50%; background: var(--sand-1);
  display: grid; place-items: center;
  transition: background-color var(--t-hover) var(--ease);
}
.xp__glyph { width: 1.1rem; height: 1.1rem; color: var(--sage-deep); }

.xp__text { display: block; min-width: 0; flex: 1 1 auto; }
.xp__title {
  display: block;
  font-family: var(--font-display);
  font-size: var(--step-1); line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
  color: var(--ink); font-weight: 600;
}
/* The date reads as a label, not as a sentence — it is the field most likely
   to be out of date, so it is the one thing on the card set apart from the
   prose around it. */
.xp__dates {
  display: block; margin-top: var(--sp-2);
  font-size: var(--step--2); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--sage-deep);
}
/* The evidence line on a place card: the Google rating with its sample size,
   and — where it applies — the fact that this one needs a car. Both sit above
   the address, because they are what a guest decides on. */
.xp__rating {
  display: inline-flex; align-items: baseline; gap: 0.3em;
  margin-top: var(--sp-2);
  font-size: var(--step--1); font-weight: 600; color: var(--ink);
}
.xp__star {
  width: 0.8rem; height: 0.8rem; color: var(--sage-deep);
  align-self: center; flex: none;
}
.xp__reviews { font-weight: 400; color: var(--ink-3); font-size: var(--step--2); }

/* Not a colour-coded warning, just a plain label. Two of the four shops are a
   drive rather than a walk, and saying so on the card is the difference
   between a useful list and a misleading one. */
.xp__tagcar {
  display: inline-block; margin: var(--sp-2) 0 0 0.5em;
  padding: 0.1rem 0.45rem;
  background: var(--sand-1); color: var(--ink-2);
  font-size: var(--step--2); font-weight: 600;
  letter-spacing: 0.03em;
}

/* The address-and-walk line. Same slot as a dated event's date, and styled
   the same way, because it answers the same question one line earlier than
   the description does. */
.xp__meta {
  display: block; margin-top: var(--sp-2);
  font-size: var(--step--2); font-weight: 600;
  text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--sage-deep);
}

.xp__body {
  display: block; margin-top: var(--sp-2);
  font-size: var(--step--1); line-height: var(--lh-body); color: var(--ink-3);
}
.xp__go {
  flex: none; color: var(--ink-4);
  transition: transform var(--t-hover) var(--ease-out), color var(--t-hover) var(--ease);
}
.xp__go .icon { width: 1rem; height: 1rem; display: block; }

@media (hover: hover) and (pointer: fine) {
  .xp:hover {
    transform: translateY(-2px);
    border-color: var(--line-2);
    box-shadow: var(--lift-1);
  }
  .xp:hover .xp__icon { background: var(--sand-2); }
  .xp:hover .xp__go { transform: translate(2px, -2px); color: var(--sage-deep); }
}
.xp:active { transform: translateY(0); }
.xp:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* ----------------------------------------------------------- booking card */
.booking-card {
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--radius); padding: clamp(1rem, 2vw, 1.5rem);
  overflow: hidden;
}
.stay-summary {
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4);
  background: var(--sand-1); border: 1px solid var(--sand-3);
  border-radius: var(--radius); padding: var(--sp-5); margin-bottom: var(--sp-5);
}
.stay-summary__items { display: flex; flex-wrap: wrap; gap: var(--sp-5); margin-right: auto; }
.stay-summary__item { display: grid; gap: 0.1rem; }
.stay-summary__label { font-size: var(--step--2); text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-3); font-weight: 600; }
.stay-summary__value { font-size: var(--step-0); color: var(--ink); font-weight: 600; }

/* --------------------------------------------------- self-built booking engine
   Step 2 of #booking-engine (assets/js/booking.js) — the quote table, the
   "special offer" strike-through, and the alternative-dates list. Everything
   here is new UI, so it earns its own small block rather than borrowing the
   admin panel's .pill (which never ships to the public site). */
.booking-result { margin-top: var(--sp-5); border-top: 1px solid var(--line); padding-top: var(--sp-5); }
.booking-result__title { font-weight: 700; font-size: var(--step-1); margin: 0 0 var(--sp-3); }
.booking-result__title--warn { color: #9A3412; }

.booking-price { width: 100%; border-collapse: collapse; margin-bottom: var(--sp-5); }
.booking-price caption { text-align: left; font-size: var(--step--1); color: var(--ink-3); margin-bottom: var(--sp-2); caption-side: top; }
.booking-price td { padding: 0.4rem 0; border-bottom: 1px solid var(--line); font-size: var(--step--1); }
.booking-price td:last-child { text-align: right; font-variant-numeric: tabular-nums; }
.booking-price tfoot td { border-bottom: none; padding-top: 0.5rem; }
.booking-price__total td { font-weight: 700; font-size: var(--step-0); border-top: 1px solid var(--line); padding-top: 0.6rem; }
.booking-price__discount { color: var(--sage-ink); }
.booking-price__orig { text-decoration: line-through; color: var(--ink-3); margin-right: 0.35em; }
.booking-price__now { font-weight: 700; color: #9E4F28; }

/* A small self-contained badge — not the admin's .pill, but the same idea. */
.pill { display: inline-flex; align-items: center; border-radius: 999px; padding: 0.1rem 0.55rem;
  font-size: var(--step--2); font-weight: 700; border: 1px solid transparent; }
.pill--ok { background: var(--sage-1, #EAF3EC); color: var(--sage-ink, #1D9E75); border-color: var(--sage-3, #BEDFC7); }

.booking-check { display: flex; align-items: center; gap: 0.5rem; font-size: var(--step--1); margin: 0.3rem 0; cursor: pointer; }
.booking-check input { flex-shrink: 0; }

/* ------------------------------------------------ booking engine: wizard
   Four steps (dates → room → extras → details). The step rail is a plain
   ordered list so it still reads as a numbered sequence with CSS off. */
.be-steps {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5);
  list-style: none; margin: 0 0 var(--sp-5); padding: 0;
}
.be-steps__item { display: flex; align-items: center; gap: var(--sp-2); color: var(--ink-3); font-size: var(--step--1); }
.be-steps__num {
  display: grid; place-items: center; width: 1.6rem; height: 1.6rem; border-radius: 999px;
  border: 1px solid var(--line); font-weight: 700; font-size: var(--step--2); flex-shrink: 0;
}
.be-steps__item.is-active { color: var(--ink); font-weight: 600; }
.be-steps__item.is-active .be-steps__num { background: var(--sage-deep, #1D9E75); border-color: var(--sage-deep, #1D9E75); color: #fff; }
.be-steps__item.is-done .be-steps__num { background: var(--sand-2, #EFE6DA); }

.be-occ-grid { display: grid; gap: var(--sp-4); grid-template-columns: repeat(auto-fit, minmax(9rem, 1fr)); }
.be-occ .input { max-width: 6rem; }
.be-promo { margin-top: var(--sp-4); max-width: 20rem; }
.be-minstay { margin: var(--sp-2) 0 0; font-size: var(--step--1); color: #9A3412; }

/* A bejelentkezett vendégnek szóló két sor a foglalás közben.
   Mindkettő ÁLLÍTÁS, nem dísz: az egyik elmondja, hogy a kedvezmény magától
   érvényesült (a csendben alacsonyabb ár gyanút kelt, nem örömet), a másik
   azt, hogy az űrlapot mi töltöttük ki a profiljából. */
.be-loyalty {
  margin: var(--sp-3) 0 0; padding: 0.6rem 0.9rem;
  font-size: var(--step--1); color: var(--ink-2);
  background: var(--sage-1); border-left: 3px solid var(--sage-deep);
}
.be-prefill {
  margin: 0 0 var(--sp-4); font-size: var(--step--1); color: var(--ink-3);
}

.be-nav { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-5); align-items: center; }
.be-nav .btn--primary { margin-left: auto; }

.be-codemsg { font-size: var(--step--1); margin: 0 0 var(--sp-4); font-weight: 600; }
.be-codemsg.is-ok { color: var(--sage-ink, #1D9E75); }
.be-codemsg.is-bad { color: #9A3412; }

/* Room recommendation cards.
   A wide row per option rather than a grid of narrow tiles: a card now shows
   a photograph, a description and the amenity chips for EACH room in the
   option, and a mixed combination ("six-person apartment + double room")
   stacks two such rows inside one card. That does not fit in a 15rem column,
   and the guest is comparing four or five options top to bottom anyway. */
.be-cards { display: grid; gap: var(--sp-4); }
.be-card {
  position: relative; border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--white); overflow: hidden;
  display: grid; grid-template-columns: 1fr; align-items: stretch;
}
@media (min-width: 52rem) { .be-card { grid-template-columns: 1fr 14rem; } }
.be-card.is-best { border-color: var(--sage-deep, #1D9E75); box-shadow: 0 0 0 1px var(--sage-deep, #1D9E75); }
.be-card.is-unavailable { opacity: 0.62; }
/* A nem foglalható lehetőség kisebb fotóval, rövidebb sorral: látszik,
   de nem foglalja el ugyanazt a helyet, mint egy választható ajánlat. */
.be-card.is-unavailable .be-card__media { aspect-ratio: 16 / 10; }
@media (min-width: 40rem) {
  .be-card.is-unavailable .be-card__room { grid-template-columns: 7rem 1fr; }
}
.be-card__badge {
  position: absolute; top: 0; right: 0; background: var(--sage-deep, #1D9E75); color: #fff;
  font-size: var(--step--2); font-weight: 700; padding: 0.2rem 0.6rem;
  border-bottom-left-radius: var(--radius); z-index: 1;
}
.be-card__body { padding: var(--sp-4); display: grid; gap: var(--sp-4); align-content: start; }

/* One room inside an option. Two of these appear for a mixed combination. */
.be-card__room { display: grid; gap: var(--sp-3); grid-template-columns: 1fr; align-items: start; }
@media (min-width: 40rem) { .be-card__room { grid-template-columns: 11rem 1fr; } }
.be-card__room + .be-card__room { border-top: 1px dashed var(--line); padding-top: var(--sp-4); }
.be-card__media {
  position: relative; display: block; border-radius: var(--radius); overflow: hidden;
  aspect-ratio: 4 / 3; background: var(--sand-1);
}
.be-card__img { width: 100%; height: 100%; object-fit: cover; display: block; }
/* Teljes szélességen a 4:3 fotó egy szoba miatt is fél képernyőt enne, egy
   háromszobás kombinációnál pedig hármat — keskeny kijelzőn laposabb. */
@media (max-width: 40rem) { .be-card__media { aspect-ratio: 16 / 9; } }
.be-card__info { display: grid; gap: var(--sp-2); align-content: start; min-width: 0; }
.be-card__title { font-size: var(--step-0); margin: 0; line-height: 1.25; }
.be-card__meta { margin: 0; font-size: var(--step--2); color: var(--ink-3); }
.be-card__teaser {
  margin: 0; font-size: var(--step--1); color: var(--ink-2); line-height: 1.45;
  display: -webkit-box; -webkit-line-clamp: 3; line-clamp: 3; -webkit-box-orient: vertical; overflow: hidden;
}
.be-card__feats { display: flex; flex-wrap: wrap; gap: 0.3rem 0.5rem; list-style: none; margin: 0; padding: 0; }
.be-card__feats li {
  display: inline-flex; align-items: center; gap: 0.25rem;
  font-size: var(--step--2); color: var(--ink-3);
  border: 1px solid var(--line); border-radius: 999px; padding: 0.1rem 0.5rem;
}
.be-card__feats .icon { width: 0.9em; height: 0.9em; }
.be-card__more { font-size: var(--step--2); justify-self: start; }

/* Price and the one action, kept together and out of the reading column. */
.be-card__foot {
  padding: var(--sp-4); display: grid; gap: var(--sp-2); align-content: end;
  border-top: 1px solid var(--line);
}
@media (min-width: 52rem) {
  .be-card__foot { border-top: 0; border-left: 1px solid var(--line); background: var(--sand-1); }
}
.be-card__promo { margin: 0; font-size: var(--step--2); font-weight: 700; color: var(--sage-ink, #1D9E75); }
.be-card__price { margin: 0; font-size: var(--step-1); }
.be-card__was { display: block; text-decoration: line-through; color: var(--ink-3); font-size: var(--step--1); font-weight: 400; }
.be-card__per { display: block; font-size: var(--step--2); color: var(--ink-3); font-weight: 400; }
.be-card__unavail { margin: 0; font-weight: 700; color: #9A3412; font-size: var(--step--1); }
.be-card__reason { margin: 0; font-size: var(--step--2); color: var(--ink-3); }

/* "+N further options" and the honest empty state. */
.be-more { margin: var(--sp-3) 0 0; font-size: var(--step--2); color: var(--ink-3); }
.be-empty { margin: 0 0 var(--sp-3); }
.be-empty__title { margin: 0 0 var(--sp-2); font-weight: 700; color: #9A3412; }

/* Extras, per room */
.be-extras { display: grid; gap: var(--sp-4); }
.be-extras__room { border: 1px solid var(--line); border-radius: var(--radius); padding: var(--sp-4); margin: 0; }
.be-extras__room legend { font-weight: 700; font-size: var(--step--1); padding: 0 var(--sp-2); }
.be-extras__total { margin-top: var(--sp-4); }
/* Az extra leírása a saját sorába kerül: egy sorban futva a díj után
   ("5 000 Ft / tartózkodásKérjük, foglaláskor jelezze.") a kettő összefolyik. */
.be-extras__desc { display: block; margin-top: 0.15rem; line-height: 1.35; }
/* A választós extra csoportcíme és a hozzá tartozó lehetőségek: a cím fölött
   levegő, a lehetőségek alatta beljebb, hogy látszódjon, mihez tartoznak. */
.be-extras__choice { margin: var(--sp-4) 0 0; }
.be-extras__choice .booking-check { padding-left: var(--sp-3); }
.be-extras__group { margin: 0 0 var(--sp-1); font-weight: 700; }

.be-summary { margin-bottom: var(--sp-5); }
.be-summary__room { margin: 0 0 var(--sp-3); font-size: var(--step--1); }
.booking-price__ifa td { color: var(--ink-3); font-size: var(--step--2); border-top: 1px dashed var(--line); }

/* Calendar day tiles carry availability + an abbreviated price. The picker
   itself is the shared component; only this group opts into decoration. */
.dr__day { display: grid; gap: 0; line-height: 1.1; }
.dr__daynum { display: block; }
.dr__price { display: block; font-size: 0.62em; color: var(--ink-3); font-variant-numeric: tabular-nums; }
.dr__day.is-full { text-decoration: line-through; color: var(--ink-3); opacity: 0.65; }
.dr__day.is-full .dr__price { visibility: hidden; }
.dr__day[data-start="true"] .dr__price,
.dr__day[data-end="true"] .dr__price { color: inherit; }

.booking-alts { display: grid; gap: var(--sp-3); }
.booking-alt {
  display: flex; align-items: center; justify-content: space-between; gap: var(--sp-3);
  background: var(--sand-1); border: 1px solid var(--sand-3); border-radius: var(--radius);
  padding: var(--sp-3) var(--sp-4); font-size: var(--step--1);
}

/* ------------------------------------------------------------------ footer
   Light, like everything else. A dark footer is the single most common way a
   "light" site turns out to have a dark section after all. */
.footer { background: var(--sand-1); border-top: 1px solid var(--sand-3); padding-block: var(--sp-8) var(--sp-6); }
.footer__grid { display: grid; gap: var(--sp-7); }
@media (min-width: 56rem) { .footer__grid { grid-template-columns: 1.4fr 1fr 1fr; gap: var(--sp-8); } }
.footer__brand { display: grid; gap: var(--sp-2); align-content: start; justify-items: start; }
/* Larger than the header's: the footer is where the brand gets to be a brand
   rather than a navigation affordance, and there is room for it. */
.footer__logo { height: 30px; width: auto; display: block; }
.footer__blurb { color: var(--ink-3); font-size: var(--step--1); max-width: 38ch; margin: var(--sp-3) 0 0; }
.footer__social {
  list-style: none; margin: var(--sp-5) 0 0; padding: 0;
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4);
}
.footer__social a {
  display: inline-flex; align-items: center; gap: 0.45em;
  font-size: var(--step--1); font-weight: 500; text-decoration: none;
  color: var(--ink-2);
}
.footer__social a:hover { color: var(--sage-ink); }
.footer__social .icon { color: var(--sage-deep); }
.footer__title { font-size: var(--step--2); text-transform: uppercase; letter-spacing: 0.08em; color: var(--ink-3); font-weight: 600; margin-bottom: var(--sp-4); }
.footer__list { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--sp-3); }
.footer__list a, .footer__list li { font-size: var(--step--1); color: var(--ink-2); text-decoration: none; }
.footer__list a:hover { color: var(--sage-ink); text-decoration: underline; }
.footer__base {
  display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: space-between;
  margin-top: var(--sp-7); padding-top: var(--sp-5); border-top: 1px solid var(--sand-3);
  font-size: var(--step--2); color: var(--ink-3);
}

/* ------------------------------------------------------------------ misc */
.consent {
  position: fixed; left: var(--gutter); bottom: var(--stack-bottom); z-index: 110;
  width: min(30rem, calc(100vw - (var(--gutter) * 2)));
  max-height: calc(100vh - var(--stack-bottom) - var(--gutter)); overflow-y: auto;
  display: flex; flex-wrap: wrap; align-items: center; gap: var(--sp-4);
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--lift-2);
  padding: var(--sp-5);
  /* The transform alone does not put this away. The note is anchored
     --stack-bottom above the viewport floor, so translating it by its own
     height plus a gutter leaves its top edge at 800px on an 844px phone — 44px
     of a closed cookie note, at full opacity, permanently parked over the
     content. It has to be hidden as well as moved, and `visibility` is what
     also takes its button out of the tab order while it is away. */
  opacity: 0; visibility: hidden;
  transform: translateY(calc(100% + var(--gutter) + 1rem));
  transition: transform 320ms var(--ease-out),
              opacity 240ms var(--ease),
              visibility 320ms;
}
.consent[data-open="true"] { opacity: 1; visibility: visible; transform: none; }
@media (prefers-reduced-motion: reduce) {
  .consent { transition-property: opacity, visibility !important; }
}
/* A hozzájárulás-kezelő (2026-09-25): a régi sütisáv helyén, ugyanazon a
   helyen és ugyanazzal az emelkedéssel — de három egyenrangú gombbal és egy
   kategóriánkénti panellel. */
.consent__title { margin: 0; flex: 1 1 100%; font-weight: 600; color: var(--ink); }
.consent__text, .consent__links { margin: 0; flex: 1 1 100%; font-size: var(--step--1); color: var(--ink-3); }
.consent__links { font-size: var(--step--2); }
.consent__actions { display: flex; flex-wrap: wrap; gap: var(--sp-2); flex: 1 1 100%; }
.consent__panel { flex: 1 1 100%; display: grid; gap: var(--sp-3); border-top: 1px solid var(--line); padding-top: var(--sp-4); }
.consent__panel[hidden] { display: none; }
.consent__cat .check { color: var(--ink-2); }
.consent__cat strong { color: var(--ink); }

/* A térkép helykitöltője: amíg a vendég nem kéri, a Google-tól semmi nem töltődik. */
.map-slot { position: relative; min-height: clamp(18rem, 42vw, 26rem); display: grid; }
.map-slot iframe { display: block; width: 100%; height: clamp(18rem, 42vw, 26rem); border: 0; }
.map-consent { display: grid; align-content: center; justify-items: start; gap: var(--sp-3); padding: var(--sp-6); }
.map-consent[hidden] { display: none; }
.map-consent .icon { width: 1.6rem; height: 1.6rem; color: var(--sage-deep); }
.map-consent__title { margin: 0; font-weight: 600; color: var(--ink); }
.map-consent__text, .map-consent__more { margin: 0; font-size: var(--step--1); color: var(--ink-3); max-width: 46ch; }
.map-consent__actions { margin: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.btn--link { background: none; border: 0; padding: 0; font: inherit; color: var(--sage-ink); text-decoration: underline; cursor: pointer; }

/* A lábléc jogi sora — mindig látható, és benne a süti-beállítások. */
.footer__legal { margin-top: var(--sp-7); }
.footer__legal-list { list-style: none; margin: 0; padding: 0; display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5); }
.footer__legal-list a, .footer__consent { font-size: var(--step--1); color: var(--ink-2); }
.footer__consent { background: none; border: 0; padding: 0; font-family: inherit; text-decoration: underline; cursor: pointer; }
.footer__consent:hover, .footer__legal-list a:hover { color: var(--sage-ink); }
.footer__legal + .footer__base { margin-top: var(--sp-5); }

/* A foglalás 4. lépésének fizetési és ÁSZF-blokkja. */
.be-pay { margin-top: var(--sp-5); border-top: 1px solid var(--line); padding-top: var(--sp-4); }
.be-pay dl { margin: 0; display: grid; gap: var(--sp-2); }
.be-pay dt { font-size: var(--step--2); text-transform: uppercase; letter-spacing: 0.06em; color: var(--ink-3); }
.be-pay dd { margin: 0 0 var(--sp-2); color: var(--ink-2); font-size: var(--step--1); }
.be-terms { margin-top: var(--sp-4); display: grid; gap: var(--sp-2); }
.be-bank { margin-top: var(--sp-4); font-size: var(--step--1); }
.be-bank table td { padding: 0.2rem 0.6rem 0.2rem 0; }

/* SZÉP-kártyás előleg — kibocsátói útmutató (foglalóűrlap, visszaigazolás, feltételek). */
.be-szep { margin-top: var(--sp-3); font-size: var(--step--1); color: var(--ink-2); }
.be-szep--open { margin-top: var(--sp-5); border-top: 1px solid var(--line); padding-top: var(--sp-4); }
.be-szep summary { cursor: pointer; color: var(--sage-ink); font-weight: 600; }
.be-szep p { margin: var(--sp-2) 0; }
.be-szep__steps { margin: var(--sp-2) 0; padding-left: 1.3rem; }
.be-szep__steps li { margin-bottom: 0.25rem; }
.be-szep__list { list-style: none; margin: var(--sp-3) 0; padding: 0; display: grid; gap: var(--sp-3); }
.be-szep__list li { border-left: 3px solid var(--sage-deep); padding: 0.1rem 0 0.1rem var(--sp-3); overflow-wrap: anywhere; }
.be-szep__list a { color: var(--sage-ink); }

/* Jogi oldalak. */
.legal { max-width: 46rem; }
.legal h2 { margin-top: var(--sp-7); }
.legal__draft { border: 1px solid var(--sand-3); background: var(--sand-1); padding: var(--sp-4); margin: 0 0 var(--sp-5); }
.legal__meta { font-size: var(--step--1); color: var(--ink-3); }
.legal table { width: 100%; border-collapse: collapse; font-size: var(--step--1); margin: var(--sp-4) 0; }
.legal th, .legal td { text-align: left; vertical-align: top; border-bottom: 1px solid var(--line); padding: 0.5rem 0.5rem 0.5rem 0; }
.legal .todo { background: #FFF3C4; padding: 0 0.2em; }
.legal-wrap { overflow-x: auto; }

/* ------------------------------------------------------- newsletter popup
   Same corner, same ground, same lift and the same entrance as the cookie
   note, because it is the same kind of object: a small card that arrives at
   the edge of the page, says one thing and can be sent away. It is not a
   modal, it has no scrim, and it never covers the column the guest is
   reading.

   Bottom LEFT, which is the one corner of this site that is not already
   spoken for: the bottom-right carries the WhatsApp button and the
   scroll-to-top above it, and below 68rem the sticky booking bar owns the
   bottom edge — a clearance --stack-bottom already accounts for. The only
   other occupant of this corner is the cookie note, and site.js will not open
   the popup while that note is up.

   z-index sits between the WhatsApp button (84) and the cookie note (110):
   above the furniture, below the question that has to be answered first. */
.nlpop {
  position: fixed; left: var(--gutter); bottom: var(--stack-bottom); z-index: 105;
  width: min(23rem, calc(100vw - (var(--gutter) * 2)));
  /* A short landscape phone must not get a card taller than the screen. */
  max-height: calc(100vh - var(--stack-bottom) - var(--gutter));
  overflow-y: auto;
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--lift-2);
  padding: var(--sp-5);
  /* Closed: parked below its own edge, invisible, and out of the tab order.
     `visibility` is what does that last part — an opacity-0 card still takes
     tab stops, and a form the guest cannot see is the worst possible thing to
     land on with a keyboard. */
  opacity: 0; visibility: hidden;
  transform: translateY(calc(100% + var(--gutter) + 1rem));
  transition: transform 380ms var(--ease-out),
              opacity 240ms var(--ease),
              visibility 380ms;
}
.nlpop[data-open="true"] { opacity: 1; visibility: visible; transform: none; }

/* Under prefers-reduced-motion the global rule already drops `transform` from
   every transition list, so the card stops sliding and simply fades in where
   it belongs. `visibility` has to be restated here: it is not in that rule's
   allowed property list, so without this the card would be a frame late. */
@media (prefers-reduced-motion: reduce) {
  .nlpop { transition-property: opacity, visibility !important; }
}

.nlpop__close {
  position: absolute; top: var(--sp-3); right: var(--sp-3);
  width: 2rem; height: 2rem; border-radius: 50%;
  border: 0; background: transparent; color: var(--ink-3);
  display: grid; place-items: center;
  transition: background-color var(--dur) var(--ease), color var(--dur) var(--ease);
}
.nlpop__close:hover { background: var(--sand-1); color: var(--ink); }
.nlpop__close .icon { width: 1.05rem; height: 1.05rem; }

.nlpop__title {
  margin: 0 2rem 0 0;
  font-family: var(--font-display);
  /* --step-2 is the h3 size. A corner card is not a section, and this is the
     smallest step that still reads as a heading beside 13px body copy. */
  font-size: var(--step-2); line-height: var(--lh-head);
  letter-spacing: var(--ls-head);
  color: var(--ink); font-weight: 600;
}
.nlpop__lead {
  margin: var(--sp-2) 0 var(--sp-4);
  font-size: var(--step--1); color: var(--ink-3);
}
/* Stacked, not side by side. The card is 286px wide on a 390px phone once it
   has cleared the WhatsApp column, and an input with a button beside it at
   that width leaves neither of them usable. */
.nlpop__row { display: grid; gap: var(--sp-3); }
.nlpop__row .btn { width: 100%; }
.nlpop__form .check { margin-top: var(--sp-3); font-size: var(--step--2); }
.nlpop__form .form-status:empty { display: none; }

@media (max-width: 47.99rem) {
  /* Stop short of the bottom-right column instead of being drawn over it,
     exactly as the cookie note does. Reserving --fab-size clears the WhatsApp
     button and the narrower scroll-to-top above it in one go, at any height
     the card happens to grow to. */
  .nlpop {
    width: auto;
    right: calc(var(--gutter) + var(--fab-size) + var(--fab-gap));
  }
}

.to-top {
  position: fixed; right: var(--gutter);
  bottom: calc(var(--stack-bottom) + var(--fab-step)); z-index: 80;
  width: 2.75rem; height: 2.75rem; border-radius: 50%;
  border: 1px solid var(--line); background: rgba(255,254,252,0.94);
  backdrop-filter: blur(8px); color: var(--ink);
  display: grid; place-items: center; box-shadow: var(--lift-1);
  opacity: 0; visibility: hidden; transform: translateY(8px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease), visibility var(--dur);
}
.to-top[data-show="true"] { opacity: 1; visibility: visible; transform: none; }
/* The clearance for the sticky booking bar now lives in --stack-bottom, so
   this element no longer carries its own copy of that number. */

/* ----------------------------------------------------------------- motion
   Two rules only: reveal-on-scroll for real lists, and the hero entrance.
   Everything animates transform/opacity so it stays on the compositor, and
   everything is switched off under prefers-reduced-motion. */
[data-reveal="on"] .stagger > * {
  opacity: 0; transform: translateY(14px);
  transition: opacity var(--t-reveal) var(--ease-out),
              transform var(--t-reveal) var(--ease-out);
  /* 60ms between items, capped at eight. A gallery of 57 tiles would otherwise
     finish its cascade three and a half seconds after it started, which reads
     as the page being slow rather than as choreography. */
  transition-delay: calc(min(var(--i, 0), 8) * 60ms);
}
[data-reveal="on"] .stagger.is-in > * { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 1ms !important;
    animation-delay: 0ms !important;
    animation-iteration-count: 1 !important;
    /* Reduced motion means fewer and gentler animations, not none. Colour and
       opacity keep fading — they carry meaning and move nothing. Restricting
       transition-property is what removes the movement: any transform still
       applies, it just arrives instantly instead of travelling. */
    transition-property: opacity, color, background-color, border-color,
                         box-shadow, fill, stroke !important;
    transition-duration: 140ms !important;
  }
  [data-reveal="on"] .stagger > * { opacity: 1; transform: none; }
  .hero__slide { transition: none; }
}

/* Cross-document transitions keep the header, footer and page ground
   continuous between the sixty static pages, so the site reads as one place
   rather than a stack of documents. */
@view-transition { navigation: auto; }
.header { view-transition-name: site-header; }
.footer { view-transition-name: site-footer; }

/* Print: the booking details are the thing people actually print. */
@media print {
  .header, .drawer, .stickybar, .consent, .nlpop, .to-top, .hero__dots, .footer__social { display: none !important; }
  body { color: #000; }
  .hero { min-height: auto; }
}

/* The one sentence in a room description a guest must not skim past. Bold
   plus a touch more ink than the paragraph around it — not a coloured
   warning, because it is a fact about the room, not an alert. */
.room__caveat { font-weight: 600; color: var(--ink); }

/* ------------------------------------------------------------- pet policy
   The sitewide pet line: under the three reasons on the homepage and the
   about page, and under the capacity note on each room page.

   A hairline above it and a paw beside it, and nothing else. It was drafted
   as a tinted callout first, which is what turned it into the loud banner the
   brief specifically did not want — a coloured box in the middle of a calm
   page announces a promotion, and this is a fact about the house. The rule is
   there so it reads as a footnote to the section rather than as another
   paragraph of it. */
.pet-note {
  display: flex; align-items: baseline; gap: 0.6em;
  margin: var(--sp-6) 0 0;
  padding-top: var(--sp-5);
  border-top: 1px solid var(--line);
  font-size: var(--step--1); color: var(--ink-3);
  max-width: 58ch;
}
.pet-note .icon {
  width: 1rem; height: 1rem; flex: none;
  color: var(--sage-deep); transform: translateY(0.15em);
}
/* On a room page it follows a hint line inside a column, not a three-up grid,
   so it wants the tighter rhythm of the text it sits under. */
.about .pet-note { margin-top: var(--sp-5); padding-top: var(--sp-4); }

/* ------------------------------------------------------------- scroll cue
   A mark at the foot of the homepage hero. Deliberately not the word
   "Scroll": a visitor looking at a hero knows what a page does, and labelling
   it is the tell that gives away a templated site. It is a chevron, it moves
   about six pixels, and it removes itself the moment the guest scrolls — an
   invitation that stops asking once it has been accepted.

   Homepage only. The subpage banners are short enough that the content below
   is already visible, so a cue there would be pointing at nothing. */
.scrollcue {
  display: grid; place-items: center;
  width: 2.25rem; height: 2.25rem; margin: var(--sp-4) auto 0;
  border-radius: 50%;
  color: var(--ink-4);
  opacity: 0;
  pointer-events: none;
  transition: opacity var(--t-panel) var(--ease);
}
.scrollcue[data-show="true"] { opacity: 1; }
.scrollcue .icon { width: 1.35rem; height: 1.35rem; }
@media (prefers-reduced-motion: no-preference) {
  .scrollcue[data-show="true"] .icon { animation: cueNudge 2.4s var(--ease-in-out) infinite; }
}
/* Under reduced motion the chevron stays, and stays still. */
@media (prefers-reduced-motion: reduce) {
  .scrollcue[data-show="true"] .icon { animation: none; transform: none; }
}
@keyframes cueNudge {
  0%, 62%, 100% { transform: translateY(0); }
  76%           { transform: translateY(6px); }
  88%           { transform: translateY(0); }
}
/* The hero already carries a picker and two buttons on a phone; a cue under
   all of that has nothing left to invite. */
@media (max-width: 47.99rem) { .scrollcue { display: none; } }

/* ------------------------------------------------- form success + counters
   The status line already said the right words; it said them abruptly. The
   tick draws itself in over the same 300ms the panel family uses, so a sent
   enquiry gets a moment of acknowledgement rather than a text swap. */
.form-status {
  display: flex; align-items: center; gap: 0.5em;
  transition: color var(--t-hover) var(--ease);
}
.form-status__tick {
  width: 1.05em; height: 1.05em; flex: none;
  stroke: currentColor; stroke-width: 2.5; fill: none;
  stroke-linecap: round; stroke-linejoin: round;
  stroke-dasharray: 24; stroke-dashoffset: 24;
  opacity: 0;
}
.form-status[data-state="ok"] .form-status__tick { opacity: 1; }
@media (prefers-reduced-motion: no-preference) {
  .form-status[data-state="ok"] .form-status__tick {
    animation: tickDraw 460ms var(--ease-out) both;
  }
  .form-status[data-state="ok"] { animation: statusRise 300ms var(--ease-out) both; }
}
@keyframes tickDraw {
  from { stroke-dashoffset: 24; opacity: 1; }
  to   { stroke-dashoffset: 0;  opacity: 1; }
}
@keyframes statusRise {
  from { opacity: 0; transform: translateY(4px); }
  to   { opacity: 1; transform: none; }
}
/* A number mid-count must not change the width of the row it sits in, or the
   label under it shuffles sideways while it climbs. */
.factband__num { font-variant-numeric: tabular-nums; }

/* --------------------------------------------------------------- whatsapp
   A floating link, not a widget: no SDK, no script, nothing to load. It has
   to read as WhatsApp from across the room, which is why it keeps the brand
   green and the brand glyph rather than being restyled into the sand palette
   — a guest scanning for "how do I message them" is looking for that green
   circle, and a tasteful beige one would not be found.

   It sits at the foot of the shared bottom-right stack (see --stack-bottom),
   so the scroll-to-top button rides one step above it and the sticky booking
   bar keeps the bottom edge to itself on narrow screens. */
.whatsapp {
  position: fixed;
  right: var(--gutter);
  bottom: var(--stack-bottom);
  z-index: 84;                       /* under the drawer, cookie and lightbox */
  display: inline-flex; align-items: center; gap: 0.6rem;
  min-width: var(--fab-size);
  height: var(--fab-size);
  padding-inline: 1rem;
  justify-content: center;
  /* The one rectangle on the site that is NOT square, and it is not really a
     rectangle: collapsed it is a disc, expanded it is a pill, and both states
     are the shape a guest scans for when they are looking for "message them".
     §22 took the brand green down until a white label cleared 4.5:1 precisely
     so this could stay recognisable; squaring it now would spend that. It is
     the same "circles stay circles" exception the scroll-to-top button and
     the lightbox arrows sit under. */
  border-radius: 999px;
  /* WhatsApp green, taken down until white text on it clears 4.5:1. The
     brand's own #25D366 measures 1.98:1 against white and its darker #128C7E
     measures 4.14 — both fail a 13px semibold label, and the glyph would fail
     the 3:1 graphic threshold too. This is 5.42:1, still unmistakably the
     green messaging app, and tools/audit.py now holds it there. */
  background: #0F7A3D;
  color: #FFFFFF;
  text-decoration: none;
  font-size: var(--step--1);
  font-weight: 600;
  white-space: nowrap;
  box-shadow: 0 2px 6px rgba(20, 83, 45, 0.24), 0 14px 32px -12px rgba(20, 83, 45, 0.45);
  transition: transform var(--t-hover) var(--ease-out),
              box-shadow var(--t-hover) var(--ease),
              background-color var(--t-hover) var(--ease),
              padding var(--t-panel) var(--ease-in-out),
              gap var(--t-panel) var(--ease-in-out);
}
.whatsapp__icon { width: 1.5rem; height: 1.5rem; flex: none; }

/* The label collapses its own WIDTH, not just its opacity. opacity alone
   leaves the text occupying layout, so the flex row stayed ~190px wide inside
   a 52px circle and pushed the icon out of the visible box — the button
   rendered as an empty green disc with the glyph sitting outside it.
   max-width plus overflow removes the space as well as the ink. */
.whatsapp__label {
  display: block;
  max-width: 14rem;
  overflow: hidden;
  opacity: 1;
  transition: max-width 420ms var(--ease-in-out),
              opacity 200ms var(--ease);
}
.whatsapp[data-collapsed="true"] { padding-inline: 0; justify-content: center; gap: 0; }
.whatsapp[data-collapsed="true"] .whatsapp__label {
  max-width: 0;
  opacity: 0;
  pointer-events: none;
}

/* Once collapsed, hover and keyboard focus bring the words back. That is what
   makes the collapse safe on a desktop: the button stops being a 210px pill
   parked on top of whatever is in the bottom-right — at 1440 and 1600 that
   was the search panel's own submit button — while the label stays one
   pointer-move away for anyone who wants to confirm what the circle is. */
@media (hover: hover) and (pointer: fine) {
  .whatsapp[data-collapsed="true"]:hover,
  .whatsapp[data-collapsed="true"]:focus-visible { padding-inline: 1rem; gap: 0.6rem; }
  .whatsapp[data-collapsed="true"]:hover .whatsapp__label,
  .whatsapp[data-collapsed="true"]:focus-visible .whatsapp__label {
    max-width: 14rem; opacity: 1; pointer-events: auto;
  }
}

/* Press feedback, same numbers as every other button on the site. */
.whatsapp:active { transform: scale(0.97); }
@media (hover: hover) and (pointer: fine) {
  .whatsapp:hover {
    transform: translateY(-2px);
    background: #0B6231;
    box-shadow: 0 4px 10px rgba(20, 83, 45, 0.26), 0 20px 40px -14px rgba(20, 83, 45, 0.5);
  }
  .whatsapp:active:hover { transform: scale(0.97); }
}
.whatsapp:focus-visible { outline: 3px solid var(--focus); outline-offset: 3px; }

/* The label is shown at first so the button explains itself, then collapses to
   the icon so it stops eating the corner. `max-width` is animated rather than
   `display`, because display cannot transition — and the label keeps its own
   opacity so the text does not smear as it closes. The rules themselves are
   above, unscoped: this used to be a `max-width: 47.99rem` block, which left
   every desktop with a permanent 210px pill in the bottom-right corner.

   The cookie note is the only other thing that wants this row, and at 390px
   the expanded label plus that note is 537px of content in a 390px viewport,
   so one of them has to yield. The JS collapses the label as soon as the note
   opens (site.js, whatsapp()) — deliberately not a `:has()` rule here, because
   the browsers that lack it are exactly the older ones this needs to work in,
   and a silently-dropped selector is worse than no rule at all. */

/* The entrance. It waits for the page to settle rather than arriving with it:
   a button that appears in the same instant as the content is one more thing
   competing for the first look. 0.85 and not 0 — nothing in the real world
   appears out of nothing. */
/* FAIL-OPEN, and not by accident. The first version set `opacity: 0` on the
   button and relied on an animation to bring it back — which meant that under
   prefers-reduced-motion, where the global rule clamps every animation to
   1ms, the button never became visible at all. A contact button that is
   invisible is worse than one that appears bluntly.

   So the resting state is visible, and only the animation hides it: `both`
   fill applies the `from` keyframe during the 900ms delay, so it still waits
   for the page to settle before fading in. If the animation never runs — old
   browser, reduced motion, anything — the button is simply there. */
@media (prefers-reduced-motion: no-preference) {
  .whatsapp { animation: fabIn 420ms var(--ease-out) 900ms both; }
}
@keyframes fabIn {
  /* 0.85, never 0: nothing in the real world appears out of nothing. */
  from { opacity: 0; transform: scale(0.85) translateY(8px); }
  to   { opacity: 1; transform: none; }
}

/* The cookie note is the one thing that shares this corner's row, and it is
   wide. On narrow screens it stops short of the WhatsApp column instead of
   being drawn over it — it has a higher z-index, so without this it would
   cover the button entirely until dismissed. */
@media (max-width: 47.99rem) {
  .consent {
    width: auto;
    right: var(--gutter);
  }
}

/* ------------------------------------------------------------- date range
   The shared range picker (site.js `dateRange`). Anchored to whichever of the
   two fields was opened and appended to <body>, so it is never clipped by the
   hero's overflow or the form's grid.

   Once JS is driving, the native inputs go readonly: that suppresses Chrome's
   own picker and its segment editing, and the indicator is hidden so there is
   no second, competing affordance sitting in the field. */
.js .datefield .input[readonly]::-webkit-calendar-picker-indicator { display: none; }
.js .datefield .input[readonly] { cursor: pointer; }

.dr {
  position: absolute; z-index: 90;
  background: var(--surface); border: 1px solid var(--line-2);
  border-radius: var(--radius);
  box-shadow: 0 4px 12px rgba(33, 29, 24, 0.10), 0 24px 48px -18px rgba(33, 29, 24, 0.28);
  padding: var(--sp-4);
  opacity: 0; visibility: hidden; transform: translateY(-4px);
  transition: opacity var(--t-hover) var(--ease),
              transform var(--t-hover) var(--ease-out),
              visibility 0s linear var(--t-hover);
}
.dr[data-open="true"] {
  opacity: 1; visibility: visible; transform: none;
  transition: opacity var(--t-hover) var(--ease),
              transform var(--t-hover) var(--ease-out), visibility 0s;
}
.dr__head { display: flex; align-items: center; gap: var(--sp-3); margin-bottom: var(--sp-3); }
.dr__months-label {
  flex: 1; display: flex; justify-content: space-around; gap: var(--sp-4);
  font-weight: 600; font-size: var(--step--1); color: var(--ink);
  text-transform: capitalize;
}
.dr__nav {
  width: 2rem; height: 2rem; flex: none;
  display: flex; align-items: center; justify-content: center;
  background: transparent; border: 1px solid var(--line-strong);
  border-radius: var(--radius); color: var(--ink-2); cursor: pointer;
  transition: border-color var(--t-hover) var(--ease), color var(--t-hover) var(--ease);
}
.dr__nav svg { width: 1rem; height: 1rem; fill: none; stroke: currentColor;
               stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.dr__months { display: flex; gap: var(--sp-5); }
/* flex: 1 so each month FILLS its share of the panel. Left at the default
   `0 1 auto` the month sized to its own content, which on a narrow screen
   left the 1fr columns computing against ~195px instead of the panel's
   334px: 28px cells, well under a 44px touch target. */
.dr__month { min-width: 0; flex: 1 1 0; }
.dr__dow, .dr__grid { display: grid; grid-template-columns: repeat(7, 2.25rem); }
.dr__dow span {
  text-align: center; font-size: var(--step--2); color: var(--ink-3);
  text-transform: lowercase; padding-bottom: 0.35rem;
}
.dr__day {
  height: 2.25rem; border: 0; background: transparent; cursor: pointer;
  font-size: var(--step--1); color: var(--ink); font-variant-numeric: tabular-nums;
  position: relative; border-radius: var(--radius);
  transition: background-color var(--t-press) var(--ease), color var(--t-press) var(--ease);
}
.dr__day[disabled] { color: var(--ink-4); cursor: default; }
/* The band between the two ends. It is drawn on every day as the pointer
   moves, which is the whole affordance: the guest sees the length of the stay
   before committing to it, instead of counting nights in their head. */
.dr__day[data-in="true"] { background: var(--sage-1); color: var(--sage-ink); }
.dr__day[data-start="true"], .dr__day[data-end="true"] {
  background: var(--sage-deep); color: var(--white); font-weight: 600;
}
.dr__foot {
  display: flex; align-items: baseline; justify-content: space-between; gap: var(--sp-4);
  margin: var(--sp-3) 0 0; padding-top: var(--sp-3); border-top: 1px solid var(--line);
  font-size: var(--step--2);
}
.dr__count { color: var(--sage-ink); font-weight: 600; }
.dr__hint { color: var(--ink-3); }
@media (hover: hover) and (pointer: fine) {
  .dr__nav:hover { border-color: var(--sage-deep); color: var(--sage-deep); }
  .dr__day:not([disabled]):not([data-start="true"]):not([data-end="true"]):hover {
    background: var(--sage-3);
  }
}
.dr__day:focus-visible, .dr__nav:focus-visible {
  outline: 3px solid var(--focus); outline-offset: -2px;
}
/* Narrow: one month, and cells that grow to fill the width so they stay
   thumb-sized rather than shrinking to fit a second month that is not there. */
@media (max-width: 39.99rem) {
  .dr { left: 12px !important; right: 12px; width: auto; }
  .dr__dow, .dr__grid { grid-template-columns: repeat(7, 1fr); }
  .dr__day { height: 2.6rem; }
}
/* No reduced-motion block of its own on purpose. The global policy already
   covers this component correctly: it restricts transition-property to
   opacity and colour with !important, so the panel's translateY arrives
   instantly (no movement) while the fade — which carries meaning and moves
   nothing — is kept. A local `transition: none` here could never win against
   that !important, and if it did it would remove the fade the policy
   deliberately preserves. */

/* ── "Ez is érdekelheti" ─────────────────────────────────────────────────────
   Kontextusfüggő linkek a rokon oldalakra: a szobaoldalról a galériába, az
   Élményajánlóból vissza a szobákhoz. Az auditból jött, nem díszítésnek: a
   GYIK és a Rólunk oldalra a fejlécen kívül EGYETLEN link sem mutatott az
   oldalak törzsszövegéből, egyik nyelven sem, a szobaoldalak pedig egyenként
   egyetlen belső hivatkozást kaptak.

   Szándékosan halk. Nem kártyák, nem sáv, nem újabb CTA — a lap már hordoz
   hármat is. Egy vékony vonal fölötte, egy halvány címke, és sorban a linkek,
   ugyanabban a nyílban végződve, mint a többi másodlagos link a lapon. Aki
   végigolvasta az oldalt, itt talál tovább; aki nem, annak nem áll az útjába. */
.related {
  border-top: 1px solid var(--line);
  padding: var(--sp-5) 0;
}
.related__label {
  margin: 0 0 var(--sp-2);
  font-size: var(--step--1);
  color: var(--ink-3);
  letter-spacing: 0.02em;
}
.related__list {
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-5);
  margin: 0; padding: 0; list-style: none;
}
.related__link {
  display: inline-flex; align-items: center; gap: 0.4em;
  color: var(--sage-ink); text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 160ms var(--ease-out);
}
.related__link:hover { border-bottom-color: var(--sage-ink); }
.related__link .icon { width: 0.85em; height: 0.85em; }

/* ── 404 ────────────────────────────────────────────────────────────────────
   A lap, ami egy 301-et valt le. Korabban minden ismeretlen URL a fooldalra
   ment at, tehat a latogato nem tudta meg, hogy elgepelte-e a cimet, a kereso
   pedig azt tanulta meg, hogy a hibas cim maga a fooldal.

   Nincs benne fenykep. Minden mas aloldal fotoval nyit, de ide valaki hibabol
   erkezett: itt a leggyorsabb ut kifele er valamit, nem egy nagy kep. Ezert
   kozepre zart, rovid, es tulnyomoreszt kijaratokbol all. A fejlec es a lablec
   viszont a rendes, tehat a teljes navigacio kezugyben marad. */
.notfound { display: grid; place-items: center; min-height: 58vh; }
.notfound__inner { max-width: 46rem; text-align: center; }
.notfound__code {
  margin: 0 0 var(--sp-3);
  font-size: var(--step--1);
  letter-spacing: 0.08em; text-transform: uppercase;
  color: var(--ink-3);
}
.notfound__title {
  font-family: var(--font-display);
  font-size: var(--step-4); line-height: 1.1;
  margin: 0 0 var(--sp-4); color: var(--ink);
}
.notfound__lead {
  margin: 0 auto var(--sp-6); max-width: 38rem;
  color: var(--ink-2); font-size: var(--step-1);
}
.notfound__actions {
  display: flex; flex-wrap: wrap; gap: var(--sp-3);
  justify-content: center; margin-bottom: var(--sp-6);
}
.notfound__help { margin: 0; font-size: var(--step--1); color: var(--ink-3); }
.notfound__help a { color: var(--sage-ink); }
@media (max-width: 47.99rem) {
  .notfound { min-height: 50vh; }
  /* A gombok egymas ala kerulnek, teljes szelessegben: harom egymas melletti
     gomb 390px-en harom keskeny oszlopra torne a szoveget. */
  .notfound__actions { flex-direction: column; align-items: stretch; }
}

/* ============================================================ booking terms
   The condensed conditions block on /online-foglalas/ (2026-08-31).

   Small type on purpose: this is reference material a guest scans mid-booking
   for one number — the pet fee, the check-out time, the cancellation deadline
   — not prose to be read through. It replaced the conditions quickfacts strip
   and the long-stay tier cards, which said less across two full sections. */
.terms {
  border: 1px solid var(--line); border-radius: var(--radius);
  background: var(--white); padding: var(--sp-5);
}
.terms__head {
  display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: baseline;
  justify-content: space-between; margin-bottom: var(--sp-4);
  padding-bottom: var(--sp-3); border-bottom: 1px solid var(--line);
}
.terms__heading { margin: 0; font-size: var(--step-1); }
.terms__grid {
  display: grid; gap: var(--sp-4) var(--sp-6);
  grid-template-columns: repeat(auto-fit, minmax(16rem, 1fr));
}
/* Columns rather than rows, so the groups keep their own vertical rhythm and
   a long list does not leave a hole beside a short one. */
@media (min-width: 60rem) { .terms__grid { column-count: 3; column-gap: var(--sp-6); display: block; } }
.terms__group { break-inside: avoid; margin: 0 0 var(--sp-5); }
.terms__title {
  display: flex; align-items: center; gap: 0.4rem; margin: 0 0 var(--sp-2);
  font-size: var(--step--1); text-transform: uppercase; letter-spacing: 0.06em;
  color: var(--sage-ink, #1D9E75);
}
.terms__title .icon { width: 1em; height: 1em; }
.terms__list { margin: 0; display: grid; gap: var(--sp-2); }
.terms__row { display: grid; gap: 0.1rem; }
.terms__label { margin: 0; font-size: var(--step--2); color: var(--ink-3); font-weight: 600; }
.terms__value { margin: 0; font-size: var(--step--1); color: var(--ink-2); line-height: 1.4; }
.terms__value strong { font-weight: 700; color: var(--ink); }
.terms__note { display: block; font-size: var(--step--2); color: var(--ink-3); line-height: 1.35; }
.terms__alert {
  display: flex; gap: var(--sp-3); align-items: flex-start; margin: var(--sp-3) 0 0;
  padding-top: var(--sp-4); border-top: 1px solid var(--line);
  font-size: var(--step--1); color: var(--ink-2); line-height: 1.45;
}
.terms__alert .icon { flex: 0 0 auto; color: var(--sage-ink, #1D9E75); margin-top: 0.15em; }
@media (max-width: 40rem) { .terms { padding: var(--sp-4); } }

/* The secondary way to the enquiry form, beside a primary booking CTA
   (2026-08-30). Deliberately not a button: it must be findable without
   competing for the click the block is actually asking for. */
.cta-aside {
  margin: var(--sp-3) 0 0; font-size: var(--step--1); color: var(--ink-3);
  display: flex; flex-wrap: wrap; align-items: baseline; gap: 0.4rem;
}
.cta-aside .link-arrow { font-size: inherit; }
.cta-strip__actions .cta-aside { flex-basis: 100%; margin-top: var(--sp-2); }
.cta-panel .cta-aside { justify-content: center; margin-top: var(--sp-4); }
.roombar .cta-aside { margin-top: var(--sp-2); }

/* "The room you were viewing" — why this card is first. */
.be-card__from {
  position: absolute; top: 0; left: 0; background: var(--sand-1); color: var(--ink-2);
  font-size: var(--step--2); font-weight: 600; padding: 0.2rem 0.6rem;
  border-bottom-right-radius: var(--radius); border-right: 1px solid var(--line);
  border-bottom: 1px solid var(--line); z-index: 1;
}

/* ================================================ room details, in the flow
   Opened from a recommendation card (2026-08-30). It used to be a link to the
   room's own page, which threw away the dates, party size and selection the
   guest had already entered. The full page is still one click away — from
   inside here, as a deliberate step. */
.be-modal {
  position: fixed; inset: 0; z-index: 100; display: grid; place-items: center;
  padding: var(--sp-4); background: rgba(42, 38, 32, 0.55);
}
.be-modal[hidden] { display: none; }
.be-modal__panel {
  background: var(--white); border-radius: var(--radius);
  width: min(46rem, 100%); max-height: 90vh; display: flex; flex-direction: column;
  box-shadow: 0 1.5rem 3rem rgba(42, 38, 32, 0.28);
}
.be-modal__head {
  display: flex; align-items: flex-start; justify-content: space-between; gap: var(--sp-3);
  padding: var(--sp-4) var(--sp-5); border-bottom: 1px solid var(--line);
}
.be-modal__title { margin: 0; font-size: var(--step-1); }
.be-modal__meta { margin: 0.15rem 0 0; font-size: var(--step--2); color: var(--ink-3); }
.be-modal__close {
  border: 0; background: none; cursor: pointer; font-size: 1.6rem; line-height: 1;
  color: var(--ink-3); padding: 0 0.3rem;
}
.be-modal__close:hover { color: var(--ink); }
.be-modal__body { padding: var(--sp-5); overflow-y: auto; display: grid; gap: var(--sp-4); }
/* A horizontal filmstrip: several photographs without turning the dialog into
   a page the guest has to scroll through to reach the close button. */
.be-modal__photos {
  display: grid; grid-auto-flow: column; grid-auto-columns: min(22rem, 82%);
  gap: var(--sp-3); overflow-x: auto; scroll-snap-type: x mandatory;
  padding-bottom: var(--sp-2);
}
.be-modal__img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block;
  border-radius: var(--radius); scroll-snap-align: start; background: var(--sand-1);
}
.be-modal__desc { margin: 0; color: var(--ink-2); line-height: 1.55; }
.be-modal__feats { margin: 0; }
.be-modal__sub { margin: 0; font-size: var(--step--1); text-transform: uppercase;
  letter-spacing: 0.06em; color: var(--sage-ink, #1D9E75); }
.be-modal__equip {
  margin: 0; padding-left: 1.1rem; display: grid; gap: 0.2rem;
  font-size: var(--step--1); color: var(--ink-2);
}
.be-modal__foot {
  display: flex; flex-wrap: wrap; gap: var(--sp-3); justify-content: flex-end;
  padding: var(--sp-4) var(--sp-5); border-top: 1px solid var(--line);
}
.be-card__more {
  border: 0; background: none; padding: 0; cursor: pointer; font: inherit;
}
@media (max-width: 40rem) {
  .be-modal { padding: 0; }
  .be-modal__panel { width: 100%; max-height: 100vh; border-radius: 0; }
  .be-modal__body, .be-modal__head, .be-modal__foot { padding-inline: var(--sp-4); }
}

/* Deposit and what is left for arrival — visually below the total, because
   they schedule a payment rather than change what it costs. */
.booking-price__deposit td { border-top: 1px dashed var(--line); color: var(--ink-2); }
.booking-price__onarrival td { color: var(--ink-3); font-size: var(--step--1); }

/* ═══════════════════════════════════════════════════════════ vendégprofil
   A profil (belépés, regisztráció, foglalásaim, adataim, kuponjaim) ugyanabból
   a tokenkészletből épül, mint a lap többi része — ez a blokk nem új
   designnyelvet vezet be, csak elrendezést ad neki.

   Három szabályt tart, amit az oldal máshol is tart, és amitől ez a felület a
   sajátjának látszik, nem egy bekötött adminnak:

     1. NINCS dobozrács. A foglalások és a kuponok HAJSZÁLVONALLAL elválasztott
        sorok, nem árnyékos kártyák — ugyanaz a szerkesztői ritmus, amivel a
        szobasorok és a vélemények készültek.
     2. A PASZTELL SOHA NEM HORDOZ SZÖVEGET. Ami olvasható, az a --sage-ink /
        --ink ramp; a --sand-1 és a --sage-1 csak felület.
     3. Szögletes sarok mindenütt (--radius: 0).

   Az egyetlen kivétel a hűségkártya: az szándékosan egy tintelt panel, mert az
   a lap egyetlen olyan eleme, ami nem adat, hanem elismerés. Egy van belőle. */

/* ── lapfej ─────────────────────────────────────────────────────────────────
   Nem a többi oldal fotós mastheadje. A vendég azért jött, hogy elintézzen
   valamit; egy fél képernyős fénykép itt nem hangulat, hanem görgetés. */
.acc-head {
  background: var(--sand-1);
  border-bottom: 1px solid var(--sand-3);
  padding-block: clamp(2rem, 5vw, 3.25rem);
  margin-top: var(--header-h);
}
.acc-head__title {
  font-family: var(--font-display);
  font-size: var(--step-4); line-height: var(--lh-head); letter-spacing: var(--ls-head);
  color: var(--ink); margin: 0;
}

/* ── belépés / regisztráció ────────────────────────────────────────────────
   Keskeny hasáb: egy négymezős űrlapot teljes szélességben kiteríteni azt
   üzeni, hogy hosszabb, mint amilyen. */
.acc-auth { max-width: 30rem; }
.acc-lead { color: var(--ink-2); margin: 0 0 var(--sp-5); }
.acc-alt { margin: var(--sp-5) 0 0; font-size: var(--step--1); color: var(--ink-3); text-align: center; }
.acc-minor { margin: var(--sp-4) 0 0; font-size: var(--step--1); color: var(--ink-3); }
/* A :not(.btn) nem óvatosság: enélkül ez a szabály (0,1,1) legyőzi a
   .btn--primary (0,1,0) fehér feliratát, és a zöld gombra zöld szöveg kerül —
   a bekezdésbe tett gombokon pontosan ez történt. Egy komponens saját színét
   nem írhatja felül az, hogy éppen milyen bekezdésben áll. */
.acc-minor a:not(.btn) { color: var(--sage-ink); }
/* A bevezető mondat és az utána következő űrlap közé kell levegő: a
   .acc-minor csak felül kap margót, és enélkül a szöveg az első mezőcímkéhez
   tapadt. */
.acc-minor + .form, .acc-minor + .acc-coupon, .acc-minor + .acc-card { margin-top: var(--sp-5); }

/* ── fiókmenü ──────────────────────────────────────────────────────────────
   Vízszintes sáv, alatta hajszálvonal; az aktív elem egy vastagabb zöld
   vonalat kap. Kis kijelzőn vízszintesen görgethető, mert négy szó + kilépés
   360 képpontra nem fér ki, tördelve viszont két sorral tolná lejjebb a
   tartalmat minden egyes lapon. */
.acc-tabs {
  display: flex; flex-wrap: wrap; gap: 0 var(--sp-5); align-items: baseline;
  border-bottom: 1px solid var(--line);
  margin-bottom: var(--sp-6);
}
.acc-tabs__link, .acc-tabs__out {
  display: inline-block; white-space: nowrap;
  padding: 0.75rem 0; font-size: var(--step--1); font-weight: 500;
  color: var(--ink-3); border-bottom: 2px solid transparent;
  transition: color var(--t-hover) var(--ease), border-color var(--t-hover) var(--ease);
}
.acc-tabs__link:hover { color: var(--ink); border-bottom-color: var(--sand-3); }
.acc-tabs__link[aria-current="page"] { color: var(--ink); font-weight: 600; border-bottom-color: var(--sage-deep); }
/* Telefonon a kijelentkezés a többi elem UTÁN sorakozik, nem a sor jobb
   szélére lökve: tördeléskor az auto margó egyedül hagyná egy saját sorban,
   a lap túloldalán. Szélesebb kijelzőn viszont pont ott a helye. */
.acc-tabs__out { color: var(--ink-3); }
@media (min-width: 40rem) { .acc-tabs__out { margin-left: auto; } }
.acc-tabs__out:hover { color: var(--ink); }

/* ── üzenetek ───────────────────────────────────────────────────────────── */
.acc-flash {
  margin: 0 0 var(--sp-5); padding: 0.75rem 1rem;
  font-size: var(--step--1); border-left: 3px solid var(--sage-deep);
  background: var(--sage-1); color: var(--ink-2);
}
.acc-flash[data-state="error"] { border-left-color: #9A3412; background: #FDF4EF; color: #7C2D12; }

/* Az igazolatlan e-mail sávja. Nem hiba, ezért nem piros: elmondja, mit nyer
   vele a vendég, és mellette ott a gomb, amivel egy kattintással megoldható. */
.acc-notice {
  display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: center;
  background: var(--sand-1); border: 1px solid var(--sand-3);
  padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-6);
}
.acc-notice__text { margin: 0; flex: 1 1 20rem; font-size: var(--step--1); color: var(--ink-2); }
.acc-notice__form { margin: 0; }

/* ── tartalom ───────────────────────────────────────────────────────────── */
.acc-hello {
  font-family: var(--font-display); font-size: var(--step-3);
  line-height: var(--lh-head); letter-spacing: var(--ls-head);
  color: var(--ink); margin: 0 0 var(--sp-2);
}
.acc-grid { display: grid; gap: var(--sp-7); }
@media (min-width: 56rem) { .acc-grid { grid-template-columns: 1fr 1fr; gap: var(--sp-8); align-items: start; } }

.acc-block { max-width: 62rem; }
.acc-block + .acc-block { margin-top: var(--sp-7); }
/* A kéthasábos áttekintésben a hasáb maga adja a mértéket. */
.acc-grid > .acc-block { max-width: none; }
.acc-grid > .acc-block + .acc-block { margin-top: 0; }
/* Az űrlapgomb a tartalom szélességét kapná a rácsban; egy „Mentés" gomb
   viszont a szövegénél nem szélesebb. A belépő űrlapok teljes szélességű
   gombja szándékosan más: ott a gomb A lap egyetlen cselekvése. */
.acc-block .form > .btn { justify-self: start; }
.acc-block__title {
  font-size: var(--step--1); text-transform: uppercase; letter-spacing: 0.08em;
  font-weight: 600; color: var(--ink-3);
  padding-bottom: var(--sp-3); border-bottom: 1px solid var(--line);
  margin: 0 0 var(--sp-2);
}
.acc-empty { color: var(--ink-3); font-size: var(--step--1); margin: var(--sp-4) 0; }

/* Nem módosítható adat (az e-mail cím: ez a fiók azonosítója). SZÁNDÉKOSAN
   nem egy letiltott beviteli mező: az kattintásra hív, aztán nem enged. Egy
   mező, amibe nem lehet írni, ne nézzen ki mezőnek. */
.acc-readonly { display: grid; gap: 0.35rem; }
.acc-readonly__label { font-size: var(--step--1); font-weight: 500; color: var(--ink-2); }
.acc-readonly__value {
  margin: 0; padding: 0.55rem 0; color: var(--ink);
  border-bottom: 1px solid var(--line); word-break: break-all;
}

/* ── egy foglalás sora ─────────────────────────────────────────────────────
   Sor, nem doboz. A dátum viszi a súlyt, mert a vendég azt keresi; az
   összeg és az állapot a jobb hasábban, tabuláris számokkal, hogy több sor
   egymás alatt függőlegesen igazodjon. */
.acc-card {
  display: flex; flex-wrap: wrap; gap: var(--sp-4);
  align-items: baseline; justify-content: space-between;
  padding: var(--sp-5) 0; border-bottom: 1px solid var(--line);
}
.acc-card__main { flex: 1 1 16rem; }
.acc-card__dates {
  margin: 0 0 0.25rem; font-weight: 600; font-size: var(--step-1); color: var(--ink);
}
.acc-card__meta { margin: 0; font-size: var(--step--1); color: var(--ink-3); }
.acc-card__ref { font-variant-numeric: tabular-nums; }
.acc-card__side { display: grid; gap: 0.35rem; justify-items: start; }
@media (min-width: 40rem) { .acc-card__side { justify-items: end; text-align: right; } }
.acc-card__total { margin: 0; font-weight: 700; font-variant-numeric: tabular-nums; color: var(--ink); }
.acc-card__status { margin: 0; font-size: var(--step--2); color: var(--ink-3); }
.acc-card__side .btn { margin-top: var(--sp-2); }

.acc-detail__title {
  font-family: var(--font-display); font-size: var(--step-3);
  line-height: var(--lh-head); margin: var(--sp-4) 0 var(--sp-2); color: var(--ink);
}

/* ── hűségkártya ───────────────────────────────────────────────────────────
   A lap egyetlen tintelt panelje. Nem jelvénygyűjtemény és nem folyamatjelző
   sáv: egy név, egy százalék, egy mondat arról, mennyi van hátra. */
.acc-tier {
  background: var(--sand-1); border: 1px solid var(--sand-3);
  padding: var(--sp-5); margin-top: var(--sp-4);
}
.acc-tier__head { display: flex; flex-wrap: wrap; gap: var(--sp-3); align-items: center; }
.acc-tier__name {
  font-family: var(--font-display); font-size: var(--step-2);
  line-height: var(--lh-head); color: var(--ink); margin: 0;
}
.acc-tier__nights { margin: var(--sp-2) 0 0; font-size: var(--step--1); color: var(--ink-3); }
/* A még jóvá nem írt éjszakák sora. Halványabb a megszerzetteknél: ígéret,
   nem eredmény — a két szám ránézésre sem keveredhet össze. */
.acc-tier__pending { margin: 0.15rem 0 0; font-size: var(--step--2); color: var(--ink-3); }
.acc-tier__progress { margin: var(--sp-4) 0 0; font-size: var(--step--1); color: var(--ink-2); }
.acc-tier__auto { margin: var(--sp-2) 0 0; font-size: var(--step--1); color: var(--sage-ink); }
.acc-tier__explain {
  margin: var(--sp-4) 0 0; padding-top: var(--sp-4);
  border-top: 1px solid var(--sand-3);
  font-size: var(--step--2); color: var(--ink-3);
}

/* ── kuponok ───────────────────────────────────────────────────────────────
   A kód nagy, monospace és kijelölhető: telefonon diktálják, gépen bemásolják.
   Ez az egyetlen hely az oldalon, ahol fix szélességű betű indokolt. */
.acc-coupon {
  display: flex; flex-wrap: wrap; gap: var(--sp-4);
  align-items: center; justify-content: space-between;
  padding: var(--sp-5) 0; border-bottom: 1px solid var(--line);
}
.acc-coupon__main { flex: 1 1 16rem; }
.acc-coupon__value {
  margin: 0 0 0.2rem; font-family: var(--font-display);
  font-size: var(--step-2); color: var(--ink); line-height: var(--lh-head);
}
.acc-coupon__name { margin: 0 0 0.2rem; color: var(--ink-2); font-size: var(--step-0); }
.acc-coupon__side { display: grid; gap: var(--sp-3); justify-items: start; }
.acc-coupon__code {
  margin: 0; font-family: ui-monospace, SFMono-Regular, Menlo, monospace;
  font-size: var(--step-1); font-weight: 700; letter-spacing: 0.06em;
  color: var(--sage-ink); background: var(--sage-1);
  border: 1px dashed var(--sage-rule); padding: 0.4rem 0.8rem;
  user-select: all;
}

/* ── fejléc ikon-link ───────────────────────────────────────────────────────
   A hetedik menüpont után nincs hely nyolcadik SZÓNAK (lásd core.nav_items),
   de egy ikonnak van. A drawerben és a láblécben kiírva is ott van, tehát
   senkinek nem kell ikont fejtenie. */
.header__account {
  display: none; align-items: center; justify-content: center;
  width: 2.1rem; height: 2.1rem; color: var(--ink-2);
  transition: color var(--t-hover) var(--ease);
}
@media (min-width: 48rem) { .header__account { display: inline-flex; } }
.header__account:hover { color: var(--sage-deep); }
.header__account:focus-visible { outline: 3px solid var(--focus); outline-offset: 2px; }
.header__account .icon { width: 1.35rem; height: 1.35rem; }

/* ── nyomtatás ──────────────────────────────────────────────────────────────
   A vendég a visszaigazolást nyomtatja vagy PDF-be menti. Ilyenkor a
   navigáció, a sávok és a gombok nem tartalom — csak papírt fogyasztanak. */
@media print {
  .acc-head, .acc-tabs, .acc-notice, .acc-flash,
  .header, .footer, .stickybar, .to-top, .whatsapp-fab, .consent,
  .acc-detail .btn { display: none !important; }
  .acc-detail { padding: 0; }
  .section { padding-block: 0; }
}

/* ══════════════════════════════════════════════ hűségprogram: felajánlások
   Három komponens, három különböző hangerőn — és mind a három ugyanabból a
   tokenkészletből:

     .loypop   a felugró kártya (1. rész) — a leghangosabb, ezért a
               legritkább: időzítéshez, görgetéshez és elutasítás-emlékezethez
               kötve, és látogatásonként legfeljebb egy értesítés a hírlevéllel
               osztozva.
     .loynote  az egy soros említés (2. rész) — hajszálvonal és egy mondat.
     .be-auth  a foglalás közbeni belépőkártya (3. rész) — besorolt, nem modális.

   A .loypop SZÁNDÉKOSAN a .nlpop geometriáját másolja: ugyanaz a bal alsó hely,
   ugyanaz a --stack-bottom tartás (ami a sticky foglalási sávot már
   beszámítja), ugyanaz a szélesség. Két kártya, ami sosem látszik egyszerre,
   ne két különböző helyen jelenjen meg — a WhatsApp gomb és a felfelé-nyíl
   pedig a JOBB alsó sarokban ül, tehát azokkal eleve nem találkozhat. */
.loypop {
  position: fixed; left: var(--gutter); bottom: var(--stack-bottom); z-index: 104;
  width: min(23rem, calc(100vw - (var(--gutter) * 2)));
  max-height: calc(100vh - var(--stack-bottom) - var(--gutter));
  overflow-y: auto;
  background: var(--white); border: 1px solid var(--line);
  border-radius: var(--radius); box-shadow: var(--lift-2);
  padding: var(--sp-5);
  opacity: 0; visibility: hidden;
  transform: translateY(calc(100% + var(--gutter) + 1rem));
  transition: transform 380ms var(--ease-out),
              opacity 240ms var(--ease),
              visibility 380ms;
}
.loypop[data-open="true"] { opacity: 1; visibility: visible; transform: none; }

/* Csökkentett mozgás alatt a globális szabály kiveszi a `transform`-ot a
   listából, tehát a kártya nem csúszik, hanem a helyén tűnik elő. A
   `visibility` nincs abban az engedélyezett listában, ezért itt is ki kell
   mondani — enélkül egy képkockát késne. Ugyanaz a megoldás, mint a .nlpop-nál. */
@media (prefers-reduced-motion: reduce) {
  .loypop { transition-property: opacity, visibility !important; }
}

.loypop__close {
  position: absolute; top: 0.5rem; right: 0.5rem;
  display: inline-flex; align-items: center; justify-content: center;
  width: 2rem; height: 2rem; border: 0; background: transparent;
  color: var(--ink-3); border-radius: var(--radius);
}
.loypop__close:hover { background: var(--sand-1); color: var(--ink); }
.loypop__close .icon { width: 1.05rem; height: 1.05rem; }
.loypop__title {
  font-family: var(--font-display); font-size: var(--step-2);
  line-height: var(--lh-head); letter-spacing: var(--ls-head);
  color: var(--ink); margin: 0 2rem var(--sp-3) 0;
}
.loypop__body {
  margin: 0 0 var(--sp-5); font-size: var(--step--1);
  line-height: 1.55; color: var(--ink-2);
}
.loypop__alt {
  margin: var(--sp-3) 0 0; font-size: var(--step--2);
  color: var(--ink-3); text-align: center;
}
.loypop__alt a { color: var(--sage-ink); }

/* ── az egy soros említés ──────────────────────────────────────────────────
   Nem sáv, nem doboz, nem ikon: egy hajszálvonal és egy mondat. Ez a
   különbség a között, hogy megadjuk az OKOT, és a között, hogy hirdetünk. */
.loynote {
  margin: 0; padding-top: var(--sp-4);
  border-top: 1px solid var(--line);
  font-size: var(--step--1); color: var(--ink-3);
  display: flex; flex-wrap: wrap; gap: var(--sp-2) var(--sp-4); align-items: baseline;
}
.loynote__text { flex: 1 1 18rem; }
.loynote__cta { color: var(--sage-ink); font-weight: 600; white-space: nowrap; }
.loynote__cta:hover { color: var(--ink); }
/* A foglalómotor első lépése alatt és a visszaigazoláson a sor nem szekció
   határa, hanem a kártyához tartozik — ott felül is kap levegőt. */
#booking-engine .loynote { margin-top: var(--sp-5); }
.loynote--thanks { margin-top: var(--sp-5); }

/* ── belépés a foglalás közben ─────────────────────────────────────────────
   Besorolt kártya a részletek FÖLÖTT, nem modális ablak. A vendég négy lépésen
   van túl; egy elé ugró párbeszédablak itt akadály, nem ajánlat. Halvány
   homoktinten ül, hogy elkülönüljön az űrlaptól, de ne versengjen vele. */
.be-auth {
  background: var(--sand-1); border: 1px solid var(--sand-3);
  padding: var(--sp-4) var(--sp-5); margin-bottom: var(--sp-5);
}
.be-auth__title {
  margin: 0 0 0.2rem; font-weight: 700; font-size: var(--step-0); color: var(--ink);
}
.be-auth__body { margin: 0; font-size: var(--step--1); color: var(--ink-2); }
.be-auth__actions {
  display: flex; flex-wrap: wrap; gap: var(--sp-3);
  margin-top: var(--sp-4); align-items: center;
}
.be-auth__note { margin: var(--sp-2) 0 0; font-size: var(--step--2); color: var(--ink-3); }
.be-auth__form {
  display: grid; gap: var(--sp-4);
  margin-top: var(--sp-4); padding-top: var(--sp-4);
  border-top: 1px solid var(--sand-3);
  max-width: 24rem;
}
.be-auth__form .form-status:empty { display: none; }
.be-auth__done {
  margin: 0; font-size: var(--step--1); font-weight: 600; color: var(--sage-ink);
}
.be-auth[data-mode="done"] { background: var(--sage-1); border-color: var(--sage-3); }
/* Telefonon a kártya nem takarhat el mezőt: a gombok egymás alá kerülnek,
   és a kártya a görgetési sorban marad — nem fix, nem lebeg. */
@media (max-width: 30rem) {
  .be-auth__actions { flex-direction: column; align-items: stretch; }
  .be-auth__actions .btn { width: 100%; }
}

/* ── a foglaláslista jelvényei és állapota (5. rész) ─────────────────────── */
.acc-state {
  font-size: var(--step--2); font-weight: 600; color: var(--ink-3);
  text-transform: uppercase; letter-spacing: 0.06em; margin-left: 0.4rem;
  white-space: nowrap;
}
.acc-state.is-live { color: var(--sage-ink); }
.acc-state.is-off  { color: #9A3412; }
.acc-tags { margin: var(--sp-3) 0 0; display: flex; flex-wrap: wrap; gap: var(--sp-2); }
.acc-tag {
  display: inline-flex; align-items: center;
  padding: 0.1rem 0.5rem; font-size: var(--step--2); font-weight: 600;
  color: var(--ink-2); background: var(--sand-1); border: 1px solid var(--sand-3);
}
.acc-tag--sage { color: var(--sage-ink); background: var(--sage-1); border-color: var(--sage-3); }
.acc-doclink {
  display: inline-block; margin-top: var(--sp-2);
  font-size: var(--step--2); color: var(--sage-ink); font-weight: 600;
}
.acc-doclink:hover { color: var(--ink); }
.acc-detail__actions { display: flex; flex-wrap: wrap; gap: var(--sp-3); margin-top: var(--sp-5); }

/* ── dátummódosítás ───────────────────────────────────────────────────────
   A foglalás lapjának ALJÁN, a visszaigazolás alatt: a vendég először azt
   nézi meg, mit foglalt, és csak utána nyúl hozzá. Nem gomb a kártyán és nem
   modális ablak — egy nyitott, két mezős űrlap, ami magától elmondja, mit
   lehet vele csinálni.

   Az előnézet ugyanennek az űrlapnak a része, nem külön lap: az új ár ott
   jelenik meg, ahol a dátumot beírták, és a megerősítő gomb közvetlenül
   alatta van. */
.acc-modify { border-top: 1px solid var(--sand-3); padding-top: var(--sp-5); }
.acc-modify .acc-form__row {
  display: grid; gap: var(--sp-4); margin-top: var(--sp-4);
}
@media (min-width: 32rem) {
  .acc-modify .acc-form__row { grid-template-columns: 1fr 1fr; }
}
.acc-modify__act { display: flex; flex-wrap: wrap; gap: var(--sp-4); align-items: center; margin: var(--sp-4) 0 0; }
.acc-modify__preview {
  margin-top: var(--sp-4); padding: var(--sp-4);
  background: var(--sand-1); border: 1px solid var(--sand-3);
}
.acc-modify__preview > p { margin: 0 0 var(--sp-2); }
.acc-modify__preview > p:last-child { margin-bottom: 0; }
.acc-modify__dates { font-size: var(--step-1); }

/* A bal alsó kártyák és a jobb alsó gombok ütközése — MÉRVE, nem feltételezve.
   390 px-en a 350 px széles kártya (x 20–370) ráfekszik a WhatsApp gombra
   (x 318–370) és a felfelé-nyílra (x 326–370): mindkettő ott van, látszik, és
   nem lehet megnyomni.

   A megoldás nem új: a sütitájékoztató ezt már megoldotta feljebb — keskeny
   képernyőn elall a jobb oldali oszloptól ahelyett, hogy ráhúzódna. Ugyanaz a
   szabály mindkét kártyára, mert a geometriájuk azonos volt, és így az
   ütközésük is. Egy gombot elrejteni azért, mert egy kártya rajta ül, a
   rosszabbik válasz: a gomb attól még kell. */
@media (max-width: 47.99rem) {
  .nlpop, .loypop {
    width: auto;
    right: calc(var(--gutter) + var(--fab-size) + var(--fab-gap));
  }
}

/* Darabszám-választó a darabonként fizetendő extrákhoz (2026-09-25). */
.be-extras__qty { display: inline-flex; align-items: center; gap: var(--sp-2); margin: var(--sp-2) 0 0 1.6rem; font-size: var(--step--1); color: var(--ink-2); }
.be-extras__qty select { width: auto; min-width: 4rem; }
