/* === Theme Variables === */
html {
  --radius: 32px;
  --default-spacing: 18px;
  --main: #2dc450;
  --additional: #33393dff;
  --font-family: 'Comfortaa', sans-serif;
  --heading-font-family: 'Comfortaa', sans-serif;
  --bg-color: #F6F7F4;
  --text-color: #2b2f30ff;
  --heading-weight: 700;
  --heading-letter-spacing: 0.2px;
  --body-letter-spacing: 0.2px;
}

/* === Common App Styles === */
/* Layout System Custom CSS - ls- prefix */

/* === FLEXBOX === */
.ls-flex {
  display: flex;
}

.ls-inline-flex {
  display: inline-flex;
}

/* Flex Direction */

.ls-flex-column {
  flex-direction: column;
}

.ls-flex-column-reverse {
  flex-direction: column-reverse;
}

/* Flex Wrap */

.ls-wrap {
  flex-wrap: wrap;
}

.ls-no-wrap {
  flex-wrap: nowrap;
}

/* Justify Content */
.ls-justify-start {
  justify-content: flex-start;
}

.ls-justify-end {
  justify-content: flex-end;
}

.ls-justify-center {
  justify-content: center;
}

/* Align Items */
.ls-items-start {
  align-items: flex-start;
}

.ls-items-end {
  align-items: flex-end;
}

.ls-items-center {
  align-items: center;
}

.ls-gap {
  gap: 14px;
}

.ls-gap-sm {
  gap: 8px;
}

/* === POSITION === */
.ls-relative {
  position: relative;
}

/* === WIDTH & HEIGHT === */
.ls-full-width {
  width: 100%;
}

.ls-full-height {
  height: 100%;
}

/* === TEXT ALIGNMENT === */
.ls-text-left {
  text-align: left;
}

.ls-text-start {
  text-align: left;
}

.ls-text-center {
  text-align: center;
}

.ls-text-right {
  text-align: right;
}

.ls-text-end {
  text-align: right;
}

.ls-text-justify {
  text-align: justify;
}

/* === OVERFLOW === */
.ls-overflow-x-auto {
  overflow-x: auto;
}

/* === SHADOWS === */
.ls-shadow-1 {
  box-shadow: 0 1px 3px rgba(0,0,0,0.2), 0 1px 1px rgba(0,0,0,0.14), 0 2px 1px -1px rgba(0,0,0,0.12);
}

.ls-shadow-2 {
  box-shadow: 0 1px 5px rgba(0,0,0,0.2), 0 2px 2px rgba(0,0,0,0.14), 0 3px 1px -2px rgba(0,0,0,0.12);
}

.ls-shadow-3 {
  box-shadow: 0 1px 8px rgba(0,0,0,0.2), 0 3px 4px rgba(0,0,0,0.14), 0 3px 3px -2px rgba(0,0,0,0.12);
}

/* === CURSOR === */
.ls-cursor-pointer {
  cursor: pointer;
}

/* === COLUMNS === */
.ls-column-image {
  padding: 16px;
}

@media (max-width: 599px) {
  .ls-column-image {
    width: 100% !important;
  }
}

.ls-column-wrapper {
  flex: 1 1 0;
  min-width: 0;
  display: flex;
  flex-direction: column;
}

/* Allow column to expand when it contains a menu */
.ls-column-wrapper:has(.ls-menu-view) {
  min-width: fit-content;
  flex-shrink: 0;
}

/* hideOnMobile: hides the column below the md breakpoint (matches Quasar's .gt-sm cutoff) */
@media (max-width: 1023.98px) {
  .ls-hide-mobile {
    display: none !important;
  }
}

.ls-column-content {
  width: fit-content;
  max-width: 100%;
  padding: 6px;
}

nav .ls-column-content {
  padding: 2px;
}

.ls-column-elevated {
  margin: 8px;
  padding: 16px;
}

@media (max-width: 768px) {
  .ls-column-content {
    margin: 0;
  }
}

.ls-column-bordered {
  padding: 16px;
}

/* Soft, brand-tinted glow instead of a generic grey drop-shadow — always tied to var(--main)
   so it re-themes automatically with the page, no per-column color picker needed. */
.ls-column-glow {
  padding: 16px;
  box-shadow: 0 8px 28px rgb(from var(--main) r g b / 0.18);
}

/* Hard, unblurred offset shadow (neo-brutalist "sticker shadow" look) — color/distance come in
   as an inline box-shadow from getColumnContentStyles; this class only reserves enough margin
   so the offset shadow isn't clipped by a parent, matching how .ls-column-elevated reserves
   room for its own (blurred) shadow. */
.ls-column-offset-shadow {
  margin: 20px;
  padding: 16px;
}

/* Soft embossed shadow (blurred light top-left + blurred dark bottom-right) — the actual
   two-tone box-shadow is set inline by getColumnContentStyles since its blur/offset scale
   with the user's intensity setting; this class only reserves margin so the blur isn't
   clipped by a parent, same reasoning as .ls-column-offset-shadow above. */
.ls-column-neumorphic {
  margin: 32px;
  padding: 16px;
}

/* Four corner accents instead of a full outline — color/thickness come in as the
   --ls-corner-color / --ls-corner-thickness custom properties set inline by
   getColumnContentStyles. Drawn as background gradients on a single ::before (rather than
   borders on ::before/::after) so all 4 corners fit in one pseudo-element and the layer never
   collides with the column's own background-image (e.g. a gradient background fill). */
.ls-column-corner-brackets {
  position: relative;
  padding: 16px;
}

.ls-column-corner-brackets::before {
  content: '';
  position: absolute;
  inset: 6px;
  pointer-events: none;
  background-image:
    linear-gradient(var(--ls-corner-color, #9E9E9E), var(--ls-corner-color, #9E9E9E)),
    linear-gradient(var(--ls-corner-color, #9E9E9E), var(--ls-corner-color, #9E9E9E)),
    linear-gradient(var(--ls-corner-color, #9E9E9E), var(--ls-corner-color, #9E9E9E)),
    linear-gradient(var(--ls-corner-color, #9E9E9E), var(--ls-corner-color, #9E9E9E)),
    linear-gradient(var(--ls-corner-color, #9E9E9E), var(--ls-corner-color, #9E9E9E)),
    linear-gradient(var(--ls-corner-color, #9E9E9E), var(--ls-corner-color, #9E9E9E)),
    linear-gradient(var(--ls-corner-color, #9E9E9E), var(--ls-corner-color, #9E9E9E)),
    linear-gradient(var(--ls-corner-color, #9E9E9E), var(--ls-corner-color, #9E9E9E));
  background-repeat: no-repeat;
  background-size:
    22px var(--ls-corner-thickness, 2px), var(--ls-corner-thickness, 2px) 22px,
    22px var(--ls-corner-thickness, 2px), var(--ls-corner-thickness, 2px) 22px,
    22px var(--ls-corner-thickness, 2px), var(--ls-corner-thickness, 2px) 22px,
    22px var(--ls-corner-thickness, 2px), var(--ls-corner-thickness, 2px) 22px;
  background-position:
    top left, top left,
    top right, top right,
    bottom left, bottom left,
    bottom right, bottom right;
}

/* Remove max-width constraint for menu content */
.ls-column-content:has(.ls-menu-view) {
  max-width: none;
}

/* Excludes .ls-column-wrapper--no-min-width (ColumnInterface.disableMinWidth, the "Fit" column
   width mode — see ColumnBuilder.vue / getColumnMinWidth()): that mode's whole point is letting
   the column shrink to whatever its content actually needs, at every breakpoint, not just
   desktop. Forcing the content div to width:100% here would fight that — it stretches the
   content to fill the (already fit-content-sized) wrapper instead of letting the content's own
   intrinsic size (or its contentWidthPercent, if set) drive the wrapper's size as intended.
   Excluding it lets the plain `.ls-column-content { width: fit-content }` base rule — or an
   explicit contentWidthPercent inline style, which wins by specificity either way — apply on
   mobile same as desktop. */
@media (max-width: 768px) {
  .ls-column-wrapper:not(.ls-column-wrapper--no-min-width) > .ls-column-content {
    width: 100% !important;
  }
}

.ls-column-height-strict {
  overflow: hidden;
}

@media (max-width: 768px) {
  .ls-column-height-strict {
    overflow: visible !important;
  }
}

/* === CONTENT OVERFLOW RING (ColumnInterface.contentOverflowSide) ===
   Pushes .ls-column-content ~50px beyond one edge of its column box, overlaying the
   neighboring column/section. Uses `transform`, not position:absolute — the box stays in
   normal flow, so sizing and the existing align-items/justify-content alignment on
   .ls-column-wrapper are untouched; this only shifts the rendered pixels.
   Deliberately NOT supported (silently clipped) when the column or its section has an image
   background with hover-zoom/darken (.ls-column-hover-bg / .ls-section-hover-bg): those rely
   on their own `overflow: hidden` to contain the zooming ::after layer, which would otherwise
   bleed past the box on hover if relaxed here. */
.ls-column-content--overflow-top {
  transform: translateY(-50px);
}

.ls-column-content--overflow-bottom {
  transform: translateY(50px);
}

.ls-column-content--overflow-left {
  transform: translateX(-50px);
}

.ls-column-content--overflow-right {
  transform: translateX(50px);
}

/* Below this breakpoint, default (non-halfOfMaxWidth) columns are full-width and stacked
   alone — there's no real neighbor to overlay, so a sideways push would just blow out of the
   page. Only halfOfMaxWidth columns (which sit two-by-two on mobile — see
   `.ls-column-wrapper--half-width` in columnStylesUtils.ts) keep the left/right push; every
   other column's push is inert here (same silent-degradation pattern as carousel/hover-bg).
   Top/bottom need no such handling. */
@media (max-width: 768px) {
  .ls-column-content--overflow-left,
  .ls-column-content--overflow-right {
    transform: none;
  }

  .ls-column-wrapper--half-width .ls-column-content--overflow-left {
    transform: translateX(-50px);
  }

  .ls-column-wrapper--half-width .ls-column-content--overflow-right {
    transform: translateX(50px);
  }
}

/* Bumps this column's own stacking priority so the overflowing content paints above sibling
   columns (same section, including wrapped rows) regardless of DOM order, and stays correct
   even if the column itself is isolated (.ls-column-hover-bg).
   Selector is doubled up (.ls-column-wrapper.ls-column-wrapper--overflow-active) purely for
   specificity: every animated column also carries .ls-animate-item, which sets z-index: 1
   further down this file (see SCROLL-TRIGGERED ANIMATIONS). At equal specificity the later
   rule wins the cascade regardless of which is "more specific-sounding", so a plain single
   class here got silently overridden back down to 1 - tying both columns' z-index and letting
   DOM order (not overflow direction) decide who paints on top. Two classes = higher
   specificity = wins outright, independent of rule order in the file. */
.ls-column-wrapper.ls-column-wrapper--overflow-active {
  z-index: 2;
}

/* .ls-column-height-strict's overflow:hidden is just a content-clipping bound (no animated
   pseudo-layer to contain), safe to relax when the ring is active. */
.ls-column-wrapper--overflow-active.ls-column-height-strict {
  overflow: visible;
}

/* Same relaxation for a scroll-reveal section wrapping a column that uses the ring — the only
   downside is a brief cosmetic overlap during the <1s entrance transition. */
.ls-animate-section:has(.ls-column-wrapper--overflow-active) {
  overflow: visible;
}

/* Bumps the whole <section> onto its own stacking context so a top/bottom ring reliably paints
   over the previous/next section (robust even if this section itself is isolated via
   .ls-section-hover-bg) — set by sectionHasOverflowColumn() in sectionStylesUtils.ts. Sections
   have no `position` otherwise, so z-index needs it set here to take effect. */
.ls-section-has-overflow-column {
  position: relative;
  z-index: 2;
}

.ls-columns {
  display: flex;
  flex-wrap: wrap;
}

/* === HOVER BACKGROUND (SECTION/COLUMN IMAGE BACKGROUNDS) ===
   The composited background (image + tint) is moved onto a pseudo-layer via CSS custom
   properties (set inline by sectionStylesUtils.ts/columnStylesUtils.ts) instead of directly
   on the element, so transform/filter can animate on hover without affecting the text/content
   nested inside. This uses `::after`, NOT `::before` - the builder (SectionBuilder.vue/
   ColumnBuilder.vue) puts its own `edit-hover-section`/`edit-hover-column` class on this exact
   same element for its "hover to edit" highlight (see `.edit-hover-section::before` in
   app.scss), which already owns `::before`. An element only has one `::before` box, so reusing
   it here merged both rules' declarations - the builder's `opacity: 0` (meant only for its own
   highlight tint) ended up hiding this background layer too, since it's the same pseudo-box.
   The pseudo-layer sits at z-index -1, which is only guaranteed to paint just behind the
   container's own content (instead of escaping to whatever distant ancestor happens to
   establish the nearest real stacking context - e.g. rendering behind the entire page) if the
   container itself establishes its own stacking context. `position: relative` alone does NOT
   do that (z-index must be non-auto), so `isolation: isolate` is required here. */
.ls-section-hover-bg {
  position: relative;
  isolation: isolate;
  overflow: hidden;
}

.ls-section-hover-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--ls-bg-image, none);
  background-size: cover;
  background-position: var(--ls-bg-position, center);
  background-repeat: no-repeat;
  background-attachment: var(--ls-bg-attachment, scroll);
  backdrop-filter: var(--ls-bg-backdrop-filter, none);
  transition: transform 0.8s ease, filter 0.8s ease;
}

.ls-section-hover-zoom:hover::after {
  transform: scale(1.08);
}

.ls-section-hover-darken:hover::after {
  filter: brightness(0.7);
}

.ls-column-hover-bg {
  /* .ls-column-wrapper already has position: relative via the ls-relative class, but that
     alone doesn't establish a stacking context - isolation: isolate is needed so the ::after's
     z-index: -1 stays local instead of escaping to a distant ancestor (see comment above). */
  isolation: isolate;
  overflow: hidden;
}

.ls-column-hover-bg::after {
  content: '';
  position: absolute;
  inset: 0;
  z-index: -1;
  background-image: var(--ls-bg-image, none);
  background-size: cover;
  background-position: var(--ls-bg-position, center);
  background-repeat: no-repeat;
  transition: transform 0.8s ease, filter 0.8s ease;
}

.ls-column-hover-zoom:hover::after {
  transform: scale(1.08);
}

.ls-column-hover-darken:hover::after {
  filter: brightness(0.7);
}

/* === SCROLL-TRIGGERED ANIMATIONS === */

.ls-animate-section {
  overflow: hidden;
}

/* Animation class added to ls-column-wrapper */
.ls-animate-item {
  z-index: 1;
}

.ls-animate-hidden {
  opacity: 0;
}

/* Visible state with animation */
.ls-animate-visible {
  transition: opacity var(--ls-animate-duration, 0.6s) ease-out, transform var(--ls-animate-duration, 0.6s) ease-out;
}

/* Fade in animation */
.ls-animate-fade-in {
  opacity: 1;
}

/* Slide from left animation */
.ls-animate-hidden[data-animation="slide-left"],
.ls-animate-hidden[data-animation="fade-in"] {
  opacity: 0;
}

.ls-animate-hidden[data-animation="slide-left"] {
  transform: translateX(-60px);
}

.ls-animate-slide-left {
  opacity: 1;
  transform: translateX(0);
}

/* Slide from right animation (for slide-sides) */
.ls-animate-hidden[data-animation="slide-right"] {
  transform: translateX(60px);
}

.ls-animate-slide-right {
  opacity: 1;
  transform: translateX(0);
}

/* Slide from bottom animation */
.ls-animate-hidden[data-animation="slide-bottom"] {
  transform: translateY(60px);
}

.ls-animate-slide-bottom {
  opacity: 1;
  transform: translateY(0);
}

/* === ELEMENT === */
.ls-element {
  width: 100%;
}

/* === ACCORDION === */
.ls-accordion {
  border-radius: 4px;
  margin-bottom: 8px;
}

.ls-accordion-summary {
  cursor: pointer;
  padding: 10px;
  user-select: none;
  list-style: revert;
  display: list-item;
}

.ls-accordion-header {
  margin: 0;
  display: inline;
  padding: 0 10px;
}

.ls-accordion-content {
  padding: 16px;
}

/* === AVATAR === */
.ls-avatar {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  font-weight: 500;
  overflow: hidden;
}

.ls-avatar-img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

/* === RATING === */
.ls-rating {
  display: inline-flex;
  gap: 4px;
}

/* === TABLE === */
.ls-table {
  width: 100%;
  border-collapse: collapse;
}

/* === TIMELINE === */
.ls-timeline {
  display: flex;
  gap: 16px;
  position: relative;
}

.ls-timeline-line {
  display: flex;
  flex-direction: column;
  align-items: center;
  position: relative;
}

.ls-timeline-circle {
  display: flex;
  align-items: center;
  justify-content: center;
}

.ls-timeline-connector {
  width: 3px;
  flex: 1;
  min-height: 40px;
  margin-top: 8px;
}

.ls-timeline-content {
  flex: 1;
  padding-top: 8px;
  padding-bottom: 16px;
}

.ls-timeline-header {
  margin: 0 0 16px 0;
}

/* === IMAGE === */
.ls-image-wrapper {
  position: relative;
  display: block;
  width: fit-content;
  max-width: 100%;
}

.ls-image-overlay {
  position: absolute;
  inset: 0;
  pointer-events: none;
}

.ls-image {
  display: block;
  margin: 12px 0;
}

.ls-image-error {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  flex-direction: column;
  background-color: #f5f5f5;
  color: #9e9e9e;
}

.ls-image-error-icon {
  width: 48px;
  height: 48px;
  fill: currentColor;
}

.ls-image-error-text {
  margin-top: 8px;
  font-size: 12px;
}

.ls-image-lightbox-trigger,
.ls-image-link-trigger {
  cursor: pointer;
}

.ls-image-lightbox-trigger:hover img {
  opacity: 0.85;
}

.ls-image-hover-zoom {
  overflow: hidden;
}

/* Combined into one rule (rather than a separate `transition` per effect) so that when any
   combination of lightbox/hover-zoom/hover-darken is enabled together, none of their transitions
   get silently dropped - `transition` is a shorthand, so two separate same-specificity rules
   each setting it to a different subset of properties would have the later one in source order
   overwrite the earlier one entirely, leaving that property to snap instantly instead of
   animating (this is exactly what happens on the gallery/portfolio/products sections, which
   combine `openFullImage` with `hoverZoom` by default). */
.ls-image-lightbox-trigger img,
.ls-image-hover-zoom .ls-image,
.ls-image-hover-darken .ls-image {
  transition: opacity 0.2s ease, transform 0.7s ease, filter 0.7s ease;
}

.ls-image-hover-zoom:hover .ls-image {
  transform: scale(1.1);
}

.ls-image-hover-darken:hover .ls-image {
  filter: brightness(0.6);
}

/* === SEPARATOR === */
.ls-separator-wrapper {
  margin: 16px 0;
}

.ls-separator-mobile {
  display: block;
}

.ls-separator-desktop {
  display: none;
}

@media (min-width: 600px) {
  .ls-separator-mobile {
    display: none;
  }

  .ls-separator-desktop {
    display: block;
  }
}

/* "Ornament" style (SeparatorPropsInterface.style) — SeparatorView.vue renders the line itself
   with a gap in the middle (inline background gradient); this ::after fills that gap with a
   small rotated-square diamond, since inline :style can't target pseudo-elements. Sized off
   --ls-separator-ornament-size (width*3.5, set inline) so it scales with the "Grubość" slider
   like every other style. Deliberately allowed to overflow the thin line's own box (no
   overflow:hidden anywhere above) — that's what makes it read as a mark sitting on the line
   rather than being squashed into it. */
.ls-separator-mobile[data-style="ornament"],
.ls-separator-desktop[data-style="ornament"] {
  position: relative;
}

.ls-separator-mobile[data-style="ornament"]::after,
.ls-separator-desktop[data-style="ornament"]::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: var(--ls-separator-ornament-size, 8px);
  height: var(--ls-separator-ornament-size, 8px);
  background: var(--ls-separator-color, currentColor);
  transform: translate(-50%, -50%) rotate(45deg);
}

/* === BACKGROUND GRADIENT (mobile fallback) === */
/* Gradient section/column backgrounds are desktop-only. Inline styles can't contain
   @media queries, so `.ls-bg-gradient` + this override collapses the gradient into a
   fully opaque solid fill of Color 1 below the phone breakpoint. `!important` beats the
   non-!important inline background-image/background-color set by sectionStylesUtils.ts
   and columnStylesUtils.ts. */
@media (max-width: 599.98px) {
  .ls-bg-gradient {
    background-image: var(--ls-bg-mobile-image, none) !important;
    background-color: var(--ls-bg-mobile-color, transparent) !important;
    backdrop-filter: none !important;
  }
}

/* === LINK LIST === */
.ls-link-list {
  width: 100%;
}

.ls-link-list-items {
  list-style: none;
  margin: 0;
  padding: 0;
}

.ls-link-item {
  width: 100%;
  transition: opacity 0.2s;
  color: inherit;
  text-decoration: none;
  display: flex;
  align-items: center;
  gap: 8px;
}

.ls-link-item:hover {
  opacity: 0.7;
}

/* === BUTTON === */
.ls-button {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 8px;
  font-weight: 500;
  text-transform: none;
  cursor: pointer;
  text-decoration: none;
  transition: all 0.25s ease;
  position: relative;
  overflow: hidden;
}

/* Ripple layer */
.ls-button::after {
  content: '';
  position: absolute;
  inset: 0;
  background: currentColor;
  opacity: 0;
  transition: opacity 0.2s ease;
  border-radius: inherit;
}

.ls-button:hover {
  transform: translateY(-1px);
  box-shadow: 0 6px 10px rgba(0, 0, 0, 0.15) !important;
}

.ls-button:hover::after {
  opacity: 0.08;
}

.ls-button:active {
  transform: translateY(0px) scale(0.97);
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  transition: all 0.1s ease;
}

.ls-button:active::after {
  opacity: 0.15;
}

/* === BUTTONS ELEMENT (ElementTypeEnum.BUTTONS — src/spa/builder/elements/buttons) === */
/* Block-level flex row, NOT inline-flex: the column's textAlign control (ls-text-*, inherited
   down to here) can only nudge an inline-level box while it's narrower than the column. As soon
   as the buttons wrap onto a second line that box goes full-width and text-align stops doing
   anything — the items inside stay pinned to flex-start. Mirroring the inherited text-align onto
   justify-content keeps the row aligned at every width, wrapped or not. Same block-flex +
   own-justify-content pattern the CHIPS element (.ls-chip-cloud) uses. */
.ls-buttons {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 8px;
}

.ls-text-center .ls-buttons {
  justify-content: center;
}

.ls-text-right .ls-buttons,
.ls-text-end .ls-buttons {
  justify-content: flex-end;
}

/* === HEADINGS === */
h1 {
  letter-spacing: 0.00735em;
  margin: 0;
  padding: 8px 0;
  word-break: break-word;
}

h2 {
  letter-spacing: normal;
  margin: 0;
  padding: 7px 0;
  word-break: break-word;
}

h3 {
  letter-spacing: normal;
  margin: 0;
  padding: 6px 0;
}

h4 {
  letter-spacing: normal;
  margin: 0;
  padding: 5px 0;
}

h5 {
  letter-spacing: normal;
  margin: 0;
  padding: 4px 0;
}

h6 {
  letter-spacing: normal;
  margin: 0;
  padding: 2px 0;
}

/* === HEADER DECORATION ===
   HeaderPropsInterface.decoration is applied as data-decoration on the .ls-header element
   (the h1-h6 tag itself — HeaderView.vue is shared by the editor canvas and the published site,
   there's no separate string renderer like Form's renderForm.ts). 'none' (the default, and what
   pre-existing saved headers fall back to since they predate this field) matches no rule below,
   so it renders identically to a plain heading. decorationColor is carried as the
   --ls-header-decoration-color inline var (set in HeaderView.vue); every rule below falls back
   to the site's --main accent if that var is unset. Mirrors the .ls-form[data-field-style]
   convention above.

   All sizing below (width/height/margin/border/padding/border-radius) uses em, not px, so it
   scales with the header's own font-size — which HeaderView.vue sets inline per
   HeaderPropsInterface.size (see getHeaderSizeConfig in SizeTypeEnum.ts). ::before/::after
   inherit font-size from their host element, so no extra data-size wiring is needed: a bigger
   header size just means a bigger decoration. Values are tuned against the LG size's 2rem
   (32px) font-size — LG is createDefaultHeaderProps()'s default — so 1em here === 32px there. */
.ls-header[data-decoration]:not([data-decoration="none"]) {
  position: relative;
}

.ls-header[data-decoration="underline"]::after {
  content: '';
  display: block;
  width: 1.875em;
  height: 0.125em;
  margin: 0.3125em auto 0;
  border-radius: 0.0625em;
  background: var(--ls-header-decoration-color, var(--main));
}

.ls-header[data-decoration="quote"] {
  /* Makes room on the right so the corner glyph doesn't sit on top of the last letter. */
  padding-right: 0.5em;
}

.ls-header[data-decoration="quote"]::before {
  content: '„';
  position: absolute;
  top: -0.3em;
  right: 0;
  font-size: 1.6em;
  line-height: 1;
  font-family: Georgia, 'Times New Roman', serif;
  color: var(--ls-header-decoration-color, var(--main));
  pointer-events: none;
}

.ls-header[data-decoration="frame"]::before,
.ls-header[data-decoration="frame"]::after {
  content: '';
  display: block;
  height: 0.09375em;
  background: var(--ls-header-decoration-color, var(--main));
}

.ls-header[data-decoration="frame"]::before {
  margin-bottom: 0.375em;
}

.ls-header[data-decoration="frame"]::after {
  margin-top: 0.375em;
}

.ls-header[data-decoration="sideBar"] {
  padding-left: 0.5em;
}

.ls-header[data-decoration="sideBar"]::before {
  content: '';
  position: absolute;
  left: 0;
  top: 0.1em;
  bottom: 0.1em;
  width: 0.125em;
  border-radius: 0.0625em;
  background: var(--ls-header-decoration-color, var(--main));
}

.ls-header[data-decoration="highlight"] {
  display: inline-block;
  padding-left: 0.4em;
  padding-right: 0.4em;
  border-radius: 0.125em;
  background: color-mix(in srgb, var(--ls-header-decoration-color, var(--main)) 25%, transparent);
}

.ls-header[data-decoration="corners"] {
  padding: 0.325em 0.4125em;
}

.ls-header[data-decoration="corners"]::before,
.ls-header[data-decoration="corners"]::after {
  content: '';
  position: absolute;
  width: 0.875em;
  height: 0.875em;
  border: 0.09375em solid var(--ls-header-decoration-color, var(--main));
}

.ls-header[data-decoration="corners"]::before {
  top: 0;
  left: 0;
  border-right: none;
  border-bottom: none;
}

.ls-header[data-decoration="corners"]::after {
  bottom: 0;
  right: 0;
  border-left: none;
  border-top: none;
}

.ls-header[data-decoration="dot"]::after {
  content: '';
  display: block;
  width: 0.25em;
  height: 0.25em;
  margin: 0.375em auto 0;
  border-radius: 50%;
  background: var(--ls-header-decoration-color, var(--main));
}

/* Repeating wavy line via a masked shape (not a static-color background image) so it follows
   --ls-header-decoration-color like every other decoration here. */
.ls-header[data-decoration="wavy"]::after {
  content: '';
  display: block;
  width: 2.8125em;
  height: 0.375em;
  margin: 0.3125em auto 0;
  background-color: var(--ls-header-decoration-color, var(--main));
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 10'%3E%3Cpath d='M0 5 Q3.5 0 7 5 T14 5' fill='none' stroke='black' stroke-width='3'/%3E%3C/svg%3E");
  mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 14 10'%3E%3Cpath d='M0 5 Q3.5 0 7 5 T14 5' fill='none' stroke='black' stroke-width='3'/%3E%3C/svg%3E");
  -webkit-mask-repeat: repeat-x;
  mask-repeat: repeat-x;
  -webkit-mask-size: 0.5625em 0.375em;
  mask-size: 0.5625em 0.375em;
}

/* === TEXT SIZE CLASSES === */
.ls-text-subtitle1 {
  font-size: 1rem;
  line-height: 1.75rem;
  font-weight: 400;
  letter-spacing: 0.00937em;
}

.ls-text-body1 {
  font-size: 1rem;
  line-height: 1.5rem;
}

.ls-text-body2 {
  font-size: 0.875rem;
  line-height: 1.25rem;
}

/* === CONTAINER === */
.ls-container {
  margin: 0 auto;
  max-width: 1232px;
}

.ls-container--full-width {
  max-width: none;
}

/* === CAROUSEL === */
.ls-infinite-carousel-wrapper {
  width: 100%;
  overflow: hidden;
}

.ls-infinite-carousel-wrapper .ls-column-wrapper {
  padding-top: 3px;
  padding-bottom: 3px;
  margin-bottom: 10px;
}

.ls-infinite-carousel-container {
  width: 100%;
  overflow: hidden;
  position: relative;
}

.ls-infinite-carousel-track {
  display: flex;
  will-change: transform;
  transition: none;
}

/* === LIST === */
.ls-list-item {
  transition: all .2s;
  margin-bottom: 5px;
}

.ls-list-item:hover {
  font-weight: bold;
  cursor: pointer;
}

ul {
  list-style: none;
  padding: 0;
  margin: 0;
}

.ls-element ul,
.q-editor__content ul {
  list-style: disc;
  padding-left: 1.5em;
}

.ls-element ol,
.q-editor__content ol {
  list-style: decimal;
  padding-left: 1.5em;
}

.ls-menu-view ul,
.ls-link-list ul {
  list-style: none;
  padding-left: 0;
}

/* === GOOGLE MAP === */
.ls-google-map {
  max-width: 100%;
  border-radius: var(--radius);
  overflow: hidden;
  display: inline-block;
}

.ls-google-map-container {
  border-radius: var(--radius);
}

.ls-google-map-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  flex-direction: column;
  background-color: #f5f5f5;
  color: #9e9e9e;
  min-height: 400px;
  border-radius: var(--radius);
}

.ls-google-map-icon {
  width: 64px;
  height: 64px;
  fill: currentColor;
  margin-bottom: 16px;
}

.ls-google-map-fallback-text {
  font-size: 14px;
  margin-top: 8px;
}

.ls-google-map-address {
  font-size: 16px;
  font-weight: 500;
  margin-top: 12px;
  color: #666;
}

/* === YOUTUBE === */
.ls-youtube {
  max-width: 100%;
  border-radius: var(--radius);
  overflow: hidden;
  display: inline-block;
}

.ls-youtube-iframe {
  border-radius: var(--radius);
  display: block;
}

.ls-youtube-fallback {
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  flex-direction: column;
  background-color: #f5f5f5;
  color: #9e9e9e;
  min-height: 400px;
  border-radius: var(--radius);
}

.ls-youtube-icon {
  width: 64px;
  height: 64px;
  fill: currentColor;
  margin-bottom: 16px;
}

.ls-youtube-fallback-text {
  font-size: 14px;
  margin-top: 8px;
}

/* === UTILITIES === */
.ls-radius {
  border-radius: var(--radius);
}

.ls-nav-bar {
  width: 100%;
  z-index: 1000;
}

.ls-element {
  margin: var(--default-spacing) 0;
  padding: 0 calc(var(--default-spacing) / 2);
}

.ls-default-space {
  margin-top: calc(var(--default-spacing) / 2);
  margin-bottom: calc(var(--default-spacing) / 2);
}

.ls-nav-bar .ls-element {
  margin: 4px 0 !important;
}

/* Countdown Timer Sizes */
.ls-countdown-size-xs { font-size: 1.5rem; line-height: 1.6rem; }
.ls-countdown-size-sm { font-size: 2rem; line-height: 2.2rem; }
.ls-countdown-size-md { font-size: 2.8rem; line-height: 3rem; }
.ls-countdown-size-lg { font-size: 3.5rem; line-height: 3.8rem; }
.ls-countdown-size-xl { font-size: 5rem; line-height: 5rem; }

.ls-countdown-label-xs { font-size: 0.875rem; line-height: 1.25rem; }
.ls-countdown-label-sm { font-size: 1rem; line-height: 1.5rem; }
.ls-countdown-label-md { font-size: 1.2rem; line-height: 1.6rem; }
.ls-countdown-label-lg { font-size: 1.5rem; line-height: 1.8rem; }
.ls-countdown-label-xl { font-size: 1.8rem; line-height: 2rem; }

/* Countdown Timer */
.ls-countdown-container {
  text-align: center;
}

.ls-countdown-label {
  margin-bottom: 8px;
  opacity: 0.8;
}

.ls-countdown-timer {
  display: flex;
  align-items: flex-start;
  justify-content: center;
  gap: 4px;
  font-weight: bold;
  font-variant-numeric: tabular-nums;
}

.ls-countdown-unit {
  display: flex;
  flex-direction: column;
  align-items: center;
}

.ls-countdown-value {
  line-height: 1;
}

.ls-countdown-suffix {
  font-size: 0.35em;
  font-weight: normal;
  opacity: 0.7;
  text-transform: uppercase;
  letter-spacing: 0.05em;
}

.ls-countdown-separator {
  line-height: 1;
  opacity: 0.5;
}

/* ===========================
   Hero Image Carousel (Slideshow)
   =========================== */

.ls-slideshow {
  position: relative;
  overflow: hidden;
  width: 100%;
}

.ls-slideshow-track {
  display: flex;
  transition: transform 0.6s ease-in-out;
  will-change: transform;
}

/* Each column becomes a full-width slide */
.ls-slideshow-track > .ls-column-wrapper {
  min-width: 100% !important;
  flex: 0 0 100% !important;
  width: 100%;
}

.ls-slideshow-dots {
  position: absolute;
  bottom: 16px;
  left: 50%;
  transform: translateX(-50%);
  display: flex;
  gap: 8px;
  z-index: 10;
}

.ls-slideshow-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: rgba(255, 255, 255, 0.45);
  border: 2px solid rgba(255, 255, 255, 0.85);
  cursor: pointer;
  transition: background 0.3s;
  padding: 0;
}

.ls-slideshow-dot.active {
  background: rgba(255, 255, 255, 1);
}

/* === LOGO === */
.ls-logo-view {
  display: inline-flex;
  align-items: center;
}

/* Allow element containing menu to expand beyond column width */
.ls-element:has(.ls-menu-view) {
  width: fit-content;
  min-width: 100%;
}

.ls-menu-view {
  display: flex;
  align-items: center;
  min-width: 100%;
  width: fit-content;
}

/* Menu alignment based on column alignment */
.ls-items-start .ls-menu-view {
  justify-content: flex-start;
}

.ls-items-end .ls-menu-view {
  justify-content: flex-end;
}

.ls-items-center .ls-menu-view {
  justify-content: center;
}

.ls-nav-menu {
  display: none;
  align-items: center;
}

.ls-nav-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
}

@media (min-width: 600px) {
  .ls-nav-menu {
    display: flex;
  }
}

.ls-nav-parent {
  /* Visual space between the parent trigger and its dropdown. Declared here so the
     dropdown, its ::before bridge, and the parent's :hover::after bridge all share
     one value. */
  --ls-nav-dropdown-gap: 5px;
  position: relative;
  cursor: pointer;
  transition: opacity 0.2s;
  flex-shrink: 0;
}

.ls-nav-parent-text {
  display: block;
  padding: 10px 18px;
  white-space: nowrap;
  font-weight: 900;
  text-decoration: none;
  color: inherit;
}

/* Gap between trigger and dropdown (--ls-nav-dropdown-gap, set on .ls-nav-parent) is
   bridged by two transparent pseudo-elements (below) so moving the cursor across it
   never breaks the :hover chain and closes the menu. */
.ls-nav-dropdown {
  list-style: none;
  margin: 0;
  display: none;
  position: absolute;
  top: calc(100% + var(--ls-nav-dropdown-gap));
  left: 0;
  padding: 8px 0;
  border-radius: 8px;
  box-shadow: 0 4px 12px rgba(0, 0, 0, 0.15);
  z-index: 1001;
  font-size: 14px;
}

/* Invisible hover bridge over the gap. One on the parent (always exactly the trigger's
   width, so a straight-down move never drops hover) and one on the dropdown itself
   (covers the gap when the dropdown is wider than the trigger). */
.ls-nav-parent:hover::after {
  content: "";
  position: absolute;
  top: 100%;
  left: 0;
  right: 0;
  height: var(--ls-nav-dropdown-gap);
}

.ls-nav-dropdown::before {
  content: "";
  position: absolute;
  bottom: 100%;
  left: 0;
  right: 0;
  height: var(--ls-nav-dropdown-gap);
}

.ls-nav-parent:hover .ls-nav-dropdown {
  display: flex;
  flex-direction: column;
}

.ls-nav-child {
  text-decoration: none;
  white-space: nowrap;
  position: relative;
  display: block;
  text-align: start;
}

/* Regular weight vs the parent trigger's 900 (see .ls-nav-parent-text) — that weight gap,
   not a font-size gap, is what keeps the parent reading as clearly "more important" than its
   dropdown children even though both now render at the same 16px (bumped from 14px for
   legibility). A weight-based gap also holds at every "Rozmiar menu" tier, since dropdown
   items intentionally don't scale with that size picker (see MenuView.vue). */
.ls-nav-child a {
  display: block;
  padding: 12px 18px;
  text-decoration: none;
  color: inherit;
  font-weight: 400;
}

.ls-nav-menu .ls-nav-list {
  position: relative;
  z-index: 10;
}

.ls-nav-parent {
  position: relative;
}

/* Mobile menu styles */
.ls-mobile-menu {
  position: relative;
}

@media (min-width: 600px) {
  .ls-mobile-menu {
    display: none;
  }
}

/* Border is meant to separate desktop nav items; hide it once the mobile menu button takes over */
@media (max-width: 599.98px) {
  .ls-menu-view {
    border: none !important;
  }
}

.ls-mobile-menu-button {
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
}

.ls-mobile-menu-dropdown {
  list-style: none;
  margin: 0;
  /* Full-screen overlay: covers the whole viewport (nav bar included) instead of a short
     panel hanging below the menu button. Gets its own vertical scrollbar only when the
     item list is taller than the screen; overscroll-behavior stops that scroll from
     chaining to the page underneath. Background comes from the inline dropdownStyle. */
  position: fixed;
  inset: 0;
  width: 100vw;
  height: 100vh;
  height: 100dvh;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
  text-align: center;
  padding: 12px;
  z-index: 2000;
}

/* Sticky close (X) row, pinned top-right of the overlay — stays visible while the item
   list scrolls beneath it. Deliberately not an .ls-mobile-menu-item, so it inherits none
   of the item / accordion-row styling. */
.ls-mobile-menu-close {
  position: sticky;
  top: 0;
  z-index: 1;
  display: flex;
  justify-content: flex-end;
  margin-bottom: 4px;
  /* Match the overlay background so scrolled-up items don't show through behind the X. */
  background-color: inherit;
}

.ls-mobile-menu-close-btn {
  background: none;
  border: none;
  cursor: pointer;
  padding: 8px;
  display: flex;
  align-items: center;
  justify-content: center;
  color: inherit;
}

/* X drawn from two rotated bars so it needs no icon font in the static SSR output —
   same rationale as .ls-mobile-accordion-arrow. Colour follows currentColor (set to
   childTextColor inline in MenuView.vue). */
.ls-mobile-menu-close-icon {
  position: relative;
  display: block;
  width: 24px;
  height: 24px;
}

.ls-mobile-menu-close-icon::before,
.ls-mobile-menu-close-icon::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 0;
  width: 100%;
  height: 2px;
  background: currentColor;
}

.ls-mobile-menu-close-icon::before {
  transform: rotate(45deg);
}

.ls-mobile-menu-close-icon::after {
  transform: rotate(-45deg);
}

.ls-mobile-menu-item {
  margin-bottom: 8px;
}

/* The expanded mobile menu is a full-screen overlay, so its rows run noticeably larger than
   the desktop dropdown — bigger type and roomier touch targets. Parent keeps weight 900 vs
   the child's 400 so the hierarchy still reads even though both are large. */
.ls-mobile-parent-text {
  display: block;
  padding: 16px 24px;
  text-decoration: none;
  font-size: 20px;
  font-weight: 900;
}

.ls-mobile-children {
  list-style: none;
  margin: 0;
  padding: 0;
  padding-left: 12px;
}

.ls-mobile-child-text {
  display: block;
  padding: 14px 24px;
  text-decoration: none;
  font-size: 18px;
  font-weight: 400;
}

/* === MOBILE ACCORDION SUBMENU === */
/* Opt-in via MenuPropsInterface.mobileAccordion — parent rows with sub-pages collapse their
   submenu and toggle it on tap. Purely class-driven so the reactive builder canvas
   (MenuView.vue :class bindings) and the published static site (initMobileMenus() in
   siteRuntimeScript.ts toggling .ls-mobile-menu-item--expanded) share one implementation.
   Without the --accordion class every submenu stays visible exactly as before. */
.ls-mobile-menu-dropdown--accordion .ls-mobile-parent-text {
  display: flex;
  align-items: center;
  text-align: start;
}

.ls-mobile-menu-dropdown--accordion .ls-mobile-child-text {
  text-align: start;
}

.ls-mobile-menu-dropdown--accordion .ls-mobile-children {
  display: none;
}

.ls-mobile-menu-dropdown--accordion .ls-mobile-menu-item--expanded .ls-mobile-children {
  display: block;
}

/* Chevron drawn from borders so it inherits the row's text colour (currentColor) and needs
   no icon font in SSR. Points down when collapsed, flips up when the row is expanded.
   margin-left: auto pins it to the right edge without splitting the icon + label pair. */
.ls-mobile-accordion-arrow {
  flex-shrink: 0;
  width: 0.5em;
  height: 0.5em;
  margin-left: auto;
  margin-right: 4px;
  border-right: 2px solid currentColor;
  border-bottom: 2px solid currentColor;
  transform: rotate(45deg);
  transition: transform 0.2s ease;
}

.ls-mobile-menu-item--expanded .ls-mobile-accordion-arrow {
  transform: rotate(225deg);
}

/* === MENU HOVER EFFECTS === */
/* Applied to both top-level items and dropdown/mobile children via [data-hover-effect] on .ls-menu-view */

.ls-menu-view[data-hover-effect] .ls-nav-parent-text,
.ls-menu-view[data-hover-effect] .ls-nav-child-text,
.ls-menu-view[data-hover-effect] .ls-mobile-parent-text,
.ls-menu-view[data-hover-effect] .ls-mobile-child-text {
  position: relative;
}


/* Underline slide: accent line grows in under the text, flashes thicker on click */
.ls-menu-view[data-hover-effect="underline"] .ls-nav-parent-text::after,
.ls-menu-view[data-hover-effect="underline"] .ls-nav-child-text::after,
.ls-menu-view[data-hover-effect="underline"] .ls-mobile-parent-text::after,
.ls-menu-view[data-hover-effect="underline"] .ls-mobile-child-text::after {
  content: '';
  position: absolute;
  left: 0;
  right: 0;
  bottom: 2px;
  height: 2px;
  background: currentColor;
  opacity: 0.85;
  transform: scaleX(0);
  transform-origin: left;
  transition: transform 0.25s ease, height 0.15s ease, opacity 0.15s ease;
}

@media (hover: hover) {
  .ls-menu-view[data-hover-effect="underline"] .ls-nav-parent-text:hover::after,
  .ls-menu-view[data-hover-effect="underline"] .ls-nav-child-text:hover::after,
  .ls-menu-view[data-hover-effect="underline"] .ls-mobile-parent-text:hover::after,
  .ls-menu-view[data-hover-effect="underline"] .ls-mobile-child-text:hover::after {
    transform: scaleX(1);
  }
}

.ls-menu-view[data-hover-effect="underline"] .ls-nav-parent-text:active::after,
.ls-menu-view[data-hover-effect="underline"] .ls-nav-child-text:active::after,
.ls-menu-view[data-hover-effect="underline"] .ls-mobile-parent-text:active::after,
.ls-menu-view[data-hover-effect="underline"] .ls-mobile-child-text:active::after {
  transform: scaleX(1);
  height: 3px;
  opacity: 1;
}

/* Background fill: full-rectangle highlight fades in behind the text, compresses on click */
.ls-menu-view[data-hover-effect="pill"] .ls-nav-parent-text,
.ls-menu-view[data-hover-effect="pill"] .ls-nav-child-text,
.ls-menu-view[data-hover-effect="pill"] .ls-mobile-parent-text,
.ls-menu-view[data-hover-effect="pill"] .ls-mobile-child-text {
  transition: background-color 0.2s ease, transform 0.12s ease;
}

@media (hover: hover) {
  .ls-menu-view[data-hover-effect="pill"] .ls-nav-parent-text:hover,
  .ls-menu-view[data-hover-effect="pill"] .ls-nav-child-text:hover,
  .ls-menu-view[data-hover-effect="pill"] .ls-mobile-parent-text:hover,
  .ls-menu-view[data-hover-effect="pill"] .ls-mobile-child-text:hover {
    background-color: color-mix(in srgb, currentColor 15%, transparent);
  }
}

.ls-menu-view[data-hover-effect="pill"] .ls-nav-parent-text:active,
.ls-menu-view[data-hover-effect="pill"] .ls-nav-child-text:active,
.ls-menu-view[data-hover-effect="pill"] .ls-mobile-parent-text:active,
.ls-menu-view[data-hover-effect="pill"] .ls-mobile-child-text:active {
  background-color: color-mix(in srgb, currentColor 25%, transparent);
  transform: scale(0.95);
}

/* Lift & scale: text lifts and grows on hover, compresses on click */
.ls-menu-view[data-hover-effect="lift"] .ls-nav-parent-text,
.ls-menu-view[data-hover-effect="lift"] .ls-nav-child-text,
.ls-menu-view[data-hover-effect="lift"] .ls-mobile-parent-text,
.ls-menu-view[data-hover-effect="lift"] .ls-mobile-child-text {
  transition: transform 0.2s ease;
}

@media (hover: hover) {
  .ls-menu-view[data-hover-effect="lift"] .ls-nav-parent-text:hover,
  .ls-menu-view[data-hover-effect="lift"] .ls-nav-child-text:hover,
  .ls-menu-view[data-hover-effect="lift"] .ls-mobile-parent-text:hover,
  .ls-menu-view[data-hover-effect="lift"] .ls-mobile-child-text:hover {
    transform: translateY(-2px) scale(1.05);
  }
}

.ls-menu-view[data-hover-effect="lift"] .ls-nav-parent-text:active,
.ls-menu-view[data-hover-effect="lift"] .ls-nav-child-text:active,
.ls-menu-view[data-hover-effect="lift"] .ls-mobile-parent-text:active,
.ls-menu-view[data-hover-effect="lift"] .ls-mobile-child-text:active {
  transform: translateY(0) scale(0.95);
  transition: transform 0.08s ease;
}

/* Spotlight: hovered item stays full brightness, siblings within the same list dim */
.ls-menu-view[data-hover-effect="spotlight"] .ls-nav-parent-text,
.ls-menu-view[data-hover-effect="spotlight"] .ls-nav-child-text,
.ls-menu-view[data-hover-effect="spotlight"] .ls-mobile-parent-text,
.ls-menu-view[data-hover-effect="spotlight"] .ls-mobile-child-text {
  transition: opacity 0.2s ease, filter 0.15s ease;
}

@media (hover: hover) {
  .ls-menu-view[data-hover-effect="spotlight"] .ls-nav-list:hover .ls-nav-parent-text,
  .ls-menu-view[data-hover-effect="spotlight"] .ls-nav-dropdown:hover .ls-nav-child-text {
    opacity: 0.5;
  }

  .ls-menu-view[data-hover-effect="spotlight"] .ls-nav-list .ls-nav-parent-text:hover,
  .ls-menu-view[data-hover-effect="spotlight"] .ls-nav-dropdown .ls-nav-child-text:hover {
    opacity: 1;
  }
}

.ls-menu-view[data-hover-effect="spotlight"] .ls-nav-parent-text:active,
.ls-menu-view[data-hover-effect="spotlight"] .ls-nav-child-text:active,
.ls-menu-view[data-hover-effect="spotlight"] .ls-mobile-parent-text:active,
.ls-menu-view[data-hover-effect="spotlight"] .ls-mobile-child-text:active {
  opacity: 1;
  filter: brightness(1.3);
}

.ls-link-list button {
  padding: 0;
  background: none;
  border: none;
  cursor: pointer;
  font-family: inherit;
  font-size: inherit;
}

/* === FORM === */
.ls-form {
  display: flex;
  flex-direction: column;
  width: 100%;
}

.ls-form-field {
  display: flex;
  flex-direction: column;
  width: 100%;
  margin-bottom: 20px;
}

.ls-form-required {
  color: #d32f2f;
  margin-left: 2px;
}

.ls-form-label {
  display: block;
  font-size: 1.1rem;
  font-weight: 700;
  margin-bottom: 8px;
}

.ls-form-tip {
  display: block;
  font-size: 14px;
  margin-top: 4px;
  opacity: 0.7;
}

/* FormFieldTypeEnum.HEADER — a visual separator/section title between groups of fields, not a
   real input. --ls-form-border-color is the same accent variable set inline by
   useFormFieldStyle.ts/renderForm.ts for the input borders, reused here for the underline.
   font-size/line-height are overridden inline per-field by FormHeaderView.vue / renderHeader()
   from the field's `size` (SizePicker, default MD) — the 1.3rem below is just a fallback. */
.ls-form-header {
  font-size: 1.3rem;
  font-weight: 700;
  padding-bottom: 8px;
  border-bottom: 2px solid var(--ls-form-border-color, rgba(128, 128, 128, 0.3));
}

.ls-form-input,
.ls-form-select,
.ls-form-textarea {
  outline: none;
  width: 100%;
  padding: 10px 14px;
  font-size: 15px;
  font-family: var(--font-family, sans-serif);
  box-sizing: border-box;
  transition: border-color 0.2s, border-width 0.2s, background-color 0.2s, box-shadow 0.2s, padding 0.2s;
}

.ls-form-select {
  cursor: pointer;
}

.ls-form-textarea {
  resize: vertical;
  min-height: 90px;
}

.ls-form-input::placeholder,
.ls-form-textarea::placeholder {
  color: var(--ls-form-text-color, inherit);
  opacity: 0.5;
}

/* === FORM FIELD STYLE VARIANTS ===
   FormPropsInterface.fieldStyle ('bordered' | 'underline' | 'filled') is applied as
   data-field-style on the <form class="ls-form"> element - by the Vue components
   (FormEditor.vue/FormView.vue) AND by the static-site renderer (site-renderer/src/
   renderForm.ts), which share this same CSS. Only the accent color is dynamic (set inline
   as --ls-form-border-color by useFormFieldStyle.ts / renderForm.ts) - the structural look
   and its click/focus effect are plain CSS, same convention as .ls-menu-view[data-hover-effect]
   below. */
.ls-form[data-field-style="bordered"] .ls-form-input,
.ls-form[data-field-style="bordered"] .ls-form-select,
.ls-form[data-field-style="bordered"] .ls-form-textarea {
  border: 1px solid var(--ls-form-border-color, rgba(128, 128, 128, 0.5));
  background: transparent;
  /* Zero-spread baseline (instead of the default "none") so the focus ring below animates in
     smoothly over the input's own box-shadow transition — box-shadow can't interpolate cleanly
     from "none" to a real shadow, it just snaps. */
  box-shadow: 0 0 0 0 var(--ls-form-border-color, rgba(128, 128, 128, 0.5));
}

.ls-form[data-field-style="bordered"] .ls-form-input:focus,
.ls-form[data-field-style="bordered"] .ls-form-select:focus,
.ls-form[data-field-style="bordered"] .ls-form-textarea:focus {
  box-shadow: 0 0 0 2px var(--ls-form-border-color, rgba(128, 128, 128, 0.5));
}

.ls-form[data-field-style="underline"] .ls-form-input,
.ls-form[data-field-style="underline"] .ls-form-select,
.ls-form[data-field-style="underline"] .ls-form-textarea {
  border: none;
  border-bottom: 2px solid var(--ls-form-border-color, rgba(128, 128, 128, 0.5));
  border-radius: 0;
  background: transparent;
  padding-left: 2px;
  padding-right: 2px;
}

.ls-form[data-field-style="underline"] .ls-form-input:focus,
.ls-form[data-field-style="underline"] .ls-form-select:focus,
.ls-form[data-field-style="underline"] .ls-form-textarea:focus {
  border-bottom-width: 4px;
}

.ls-form[data-field-style="filled"] .ls-form-input,
.ls-form[data-field-style="filled"] .ls-form-select,
.ls-form[data-field-style="filled"] .ls-form-textarea {
  border: 1px solid transparent;
  background: color-mix(in srgb, var(--ls-form-border-color, rgba(128, 128, 128, 0.5)) 12%, transparent);
}

.ls-form[data-field-style="filled"] .ls-form-input:focus,
.ls-form[data-field-style="filled"] .ls-form-select:focus,
.ls-form[data-field-style="filled"] .ls-form-textarea:focus {
  background: color-mix(in srgb, var(--ls-form-border-color, rgba(128, 128, 128, 0.5)) 22%, transparent);
}

/* === FORM COLUMN LAYOUT ===
   FormPropsInterface.columns (1-4) is applied as data-columns on the same <form class="ls-form">
   element as data-field-style above, by the same three consumers (FormEditor.vue/FormView.vue/
   renderForm.ts). 1 column (the default) keeps the original flex column layout untouched - no
   rule needed. .ls-form-field-full (set per-field via FormFieldInterface.fullWidth, applied to
   .ls-form-field in the *View.vue components and to .form-editor-field in FormEditor.vue) makes
   that field span the full row regardless of column count; the submit button always spans the
   full row. */
.ls-form:is([data-columns="2"], [data-columns="3"], [data-columns="4"]) {
  display: grid;
  column-gap: var(--default-spacing);
}

.ls-form[data-columns="2"] {
  grid-template-columns: repeat(2, minmax(0, 1fr));
}

.ls-form[data-columns="3"] {
  grid-template-columns: repeat(3, minmax(0, 1fr));
}

.ls-form[data-columns="4"] {
  grid-template-columns: repeat(4, minmax(0, 1fr));
}

.ls-form:is([data-columns="2"], [data-columns="3"], [data-columns="4"]) > :is(.ls-form-submit, .ls-form-field-full) {
  grid-column: 1 / -1;
}

@media (max-width: 768px) {
  .ls-form:is([data-columns="2"], [data-columns="3"], [data-columns="4"]) {
    display: flex;
    flex-direction: column;
  }
}

.ls-form-radio-group,
.ls-form-checkbox-group {
  display: flex;
  flex-direction: column;
  gap: var(--default-spacing);
  /* A bit of breathing room before the tip text (or the next field, if there's no tip) — the
     option rows read visually denser than a single input/textarea/select, so the tip's own
     margin-top (in .ls-form-tip) alone felt too tight underneath them. */
  margin-bottom: 8px;
}

.ls-form-radio-group.ls-form-options-horizontal,
.ls-form-checkbox-group.ls-form-options-horizontal {
  flex-direction: row;
  flex-wrap: wrap;
}

.ls-form-radio-option,
.ls-form-checkbox-option {
  display: flex;
  align-items: center;
  gap: 8px;
  cursor: pointer;
}

.ls-form-radio-option:has(input:disabled),
.ls-form-checkbox-option:has(input:disabled) {
  cursor: not-allowed;
}

.ls-form-radio-option.ls-form-option-stacked,
.ls-form-checkbox-option.ls-form-option-stacked {
  flex-direction: column;
  align-items: flex-start;
}

.ls-form-option-control-row {
  display: flex;
  align-items: center;
  gap: 8px;
}

.ls-form-radio-box {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  appearance: none;
  -webkit-appearance: none;
  border: 2px solid #555;
  border-radius: 50%;
  background: transparent;
  position: relative;
  box-shadow: 0 0 0 0 var(--ls-form-border-color, rgba(128, 128, 128, 0.5));
  transition: border-color 0.2s, background-color 0.2s, box-shadow 0.2s;
}

.ls-form-radio-box:disabled {
  cursor: not-allowed;
}

.ls-form-radio-box:focus-visible {
  box-shadow: 0 0 0 2px var(--ls-form-border-color, rgba(128, 128, 128, 0.5));
}

.ls-form-radio-box:checked {
  border-color: #1565c0;
}

.ls-form-radio-box:checked::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  width: 10px;
  height: 10px;
  border-radius: 50%;
  background: #1565c0;
  transform: translate(-50%, -50%);
}

.ls-form-checkbox-box {
  width: 20px;
  height: 20px;
  flex-shrink: 0;
  appearance: none;
  -webkit-appearance: none;
  border: 2px solid #555;
  border-radius: 4px;
  background: transparent;
  position: relative;
  box-shadow: 0 0 0 0 var(--ls-form-border-color, rgba(128, 128, 128, 0.5));
  transition: border-color 0.2s, background-color 0.2s, box-shadow 0.2s;
}

.ls-form-checkbox-box:disabled {
  cursor: not-allowed;
}

.ls-form-checkbox-box:focus-visible {
  box-shadow: 0 0 0 2px var(--ls-form-border-color, rgba(128, 128, 128, 0.5));
}

.ls-form-checkbox-box:checked {
  background: #1565c0;
  border-color: #1565c0;
}

.ls-form-checkbox-box:checked::after {
  content: '';
  position: absolute;
  top: 45%;
  left: 50%;
  width: 5px;
  height: 9px;
  border: solid #fff;
  border-width: 0 2px 2px 0;
  transform: translate(-50%, -60%) rotate(45deg);
}

.ls-form-option-image {
  width: 32px;
  height: 32px;
  object-fit: cover;
  border-radius: 4px;
  flex-shrink: 0;
}

.ls-form-option-link {
  color: var(--main, #1565c0);
  font-weight: 600;
  font-size: 0.9em;
  text-decoration: underline;
  white-space: nowrap;
}

.ls-form-option-link:hover {
  opacity: 0.8;
}

.ls-form-submit {
  transition: opacity 0.2s;
}

.ls-form-submit:hover {
  opacity: 0.85;
}

.ls-form-acceptance-option {
  display: flex;
  align-items: flex-start;
  gap: 8px;
  cursor: pointer;
}

.ls-form-acceptance-option:has(input:disabled) {
  cursor: not-allowed;
}

/* === BLOG === */
.ls-blog-view {
  width: 100%;
}

.ls-blog-list {
  display: flex;
  flex-direction: column;
  gap: 32px;
}

.ls-blog-list .ls-blog-card {
  display: flex;
  gap: 16px;
  align-items: flex-start;
}

.ls-blog-list .ls-blog-thumbnail,
.ls-blog-list .ls-blog-thumbnail-placeholder {
  width: 200px;
  height: 150px;
  flex-shrink: 0;
  border-radius: var(--radius, 4px);
  object-fit: cover;
}

.ls-blog-grid-2 {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: 32px;
}

.ls-blog-grid-2 .ls-blog-thumbnail,
.ls-blog-grid-2 .ls-blog-thumbnail-placeholder {
  width: 100%;
  height: 160px;
  border-radius: 4px 4px 0 0;
}

.ls-blog-cards-large {
  display: flex;
  flex-direction: column;
  gap: 48px;
}

.ls-blog-cards-large .ls-blog-thumbnail,
.ls-blog-cards-large .ls-blog-thumbnail-placeholder {
  width: 100%;
  height: 240px;
  border-radius: 4px 4px 0 0;
}

.ls-blog-thumbnail-placeholder {
  display: flex;
  align-items: center;
  justify-content: center;
  background-color: #424242;
  color: #757575;
}

.ls-blog-placeholder-icon {
  width: 32px;
  height: 32px;
}

.ls-blog-card-body {
  padding: 8px;
}

.ls-blog-title {
  text-decoration: none;
  color: inherit;
  display: block;
  font-weight: 500;
}

.ls-blog-title:hover {
  text-decoration: underline;
}

.ls-blog-title-xs { font-size: 1.5rem; line-height: 1.167; }
.ls-blog-title-sm { font-size: 2.125rem; line-height: 1.235; }
.ls-blog-title-md { font-size: 3rem; line-height: 1.167; }
.ls-blog-title-lg { font-size: 3.75rem; line-height: 1.2; }
.ls-blog-title-xl { font-size: 6rem; line-height: 1.167; }

.ls-blog-date {
  font-size: 0.75rem;
  color: #9e9e9e;
  margin-top: 4px;
}

.ls-blog-summary {
  display: -webkit-box;
  -webkit-line-clamp: 3;
  -webkit-box-orient: vertical;
  overflow: hidden;
  margin: 10px 0;
}

.ls-blog-read-more {
  margin-top: 8px;
}

/* === CHIPS ELEMENT (ElementTypeEnum.CHIPS — src/spa/builder/elements/chips) === */
/* Structural only — color/padding/font-size are per-instance (size + colors are configurable
   per element) and applied inline by ChipsView.vue, same convention as ls-button/ButtonView.vue.
   max-width/margin stay here (not left to the column's align controls) because this is a
   full-width block-level flex-wrap container: the column's textAlign only shifts inline-level
   content, so on its own it can't position wrapped rows inside this box. The BUTTONS element
   (.ls-buttons) has the same shape and bridges that gap by mapping the inherited ls-text-* onto
   justify-content; chips instead just hardcode centered, hence the fixed justify-content here. */
.ls-chip-cloud {
  max-width: 960px;
  margin: 0 auto;
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
  align-items: center;
}

.ls-chip {
  display: inline-flex;
  align-items: center;
  gap: 6px;
  border-radius: 999px;
  font-weight: 700;
  white-space: nowrap;
}


/* === Public Export Styles === */
/* === Base Reset === */
* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  overflow-x: hidden;
}

body {
  font-family: var(--font-family, 'Roboto', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif);
  background-color: var(--bg-color, #ffffff);
  color: var(--text-color, #000000);
  min-width: 100px;
  min-height: 100vh;
  display: flex;
  flex-direction: column;
  position: relative;
  -ms-text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  line-height: 1.5;
  font-size: 16px;
  letter-spacing: var(--body-letter-spacing, .6px);
}

.ls-nav-bar--standard {
  position: relative;
}

.ls-nav-bar--sticky {
  position: sticky;
  top: 0;
}

.ls-nav-bar--compress {
  position: sticky;
  top: 0;
}

.ls-nav-bar--slide {
  position: sticky;
  top: 0;
}

/* Shared by every overlapping behaviour (compress, slide): floats full-width above
   the first section instead of taking up flow space. */
.ls-nav-bar--overlapping {
  top: 0;
  width: 100%;
  z-index: 100;
}

.ls-page--home .ls-nav-bar--compress.ls-nav-bar--overlapping {
  position: fixed;
  /* Promote to its own compositing layer. Without this, a `position: fixed` element on
     iOS Safari can get visually "reset" mid-transition by the native rubber-band bounce
     that fires when a fast upward fling reaches the top of the page — the padding-block
     transition below then appears to snap instead of animate. This is one-directional
     (only the scroll-up/decompress case hits the top-of-page bounce), which is why
     compress-on-scroll-down looked smooth already. */
  transform: translateZ(0);
  -webkit-transform: translateZ(0);
  backface-visibility: hidden;
  -webkit-backface-visibility: hidden;
}


.ls-nav-bar--overlapping section.section {
  transition: background-color 0.4s ease;
}

.ls-nav-bar--compress .ls-container {
  padding-block: 6px;
  transition: padding-block 0.4s ease;
}

.ls-nav-bar--compress.ls-nav-bar--scrolled .ls-container {
  padding-block: 0;
}

/* Slide: nav starts transparent and overlapping like compress, but instead of morphing
   in place, crossing the threshold going down hides it instantly (no animation) then
   reveals a fully opaque copy sliding down into place; crossing back going up animates
   the opaque nav sliding back up out of view, then instantly restores the original
   transparent nav (no slide on the way back in). Reversible but asymmetric — see
   initOverlappingNavAnimations() in siteRuntimeScript.ts. */
.ls-page--home .ls-nav-bar--slide.ls-nav-bar--overlapping {
  position: fixed;
  will-change: transform;
}

/* No transition here on purpose — the down-direction hide has to be instant. */
.ls-nav-bar--slide.ls-nav-bar--slide-hide {
  transform: translateY(-110%);
}

/* The down-direction reveal (sliding down into place) — animated. */
.ls-nav-bar--slide.ls-nav-bar--slide-in {
  transform: translateY(0);
  transition: transform 0.35s ease-out;
}

/* The up-direction hide (sliding back up out of view) — animated, distinct from
   '--slide-hide' even though they share a target transform: only one of the two should
   ever be applied at a time (JS keeps them mutually exclusive), since equal selector
   specificity would otherwise leave which transition wins up to CSS source order. */
.ls-nav-bar--slide.ls-nav-bar--slide-out {
  transform: translateY(-110%);
  transition: transform 0.3s ease-in;
}

main {
  flex: 1;
  overflow-x: hidden;
}

a {
  text-decoration: none;
  color: inherit;
}

p { margin: 8px 0; }

/* === Cookie Consent === */
.ls-cc-banner {
  position: fixed;
  bottom: 0;
  left: 0;
  right: 0;
  background: #1a1a2e;
  color: #fff;
  padding: 16px 24px;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: center;
  gap: 12px;
  z-index: 9999;
  box-shadow: 0 -2px 10px rgba(0, 0, 0, 0.3);
  transform: translateY(100%);
  transition: transform 0.3s ease-out;
}

.ls-cc-banner.ls-cc-visible {
  transform: translateY(0);
}

.ls-cc-banner.ls-cc-hidden {
  display: none;
}

.ls-cc-text {
  flex: 1;
  min-width: 260px;
  font-size: 14px;
  line-height: 1.5;
}

.ls-cc-link {
  color: var(--main, #027be3);
  text-decoration: underline;
  margin-left: 4px;
}

.ls-cc-link:hover {
  opacity: 0.8;
}

.ls-cc-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  align-items: center;
}

.ls-cc-btn {
  padding: 9px 20px;
  border-radius: var(--radius, 8px);
  font-size: 14px;
  font-weight: 600;
  cursor: pointer;
  transition: opacity 0.2s;
  white-space: nowrap;
  background: var(--main, #027be3);
  color: #fff;
  border: 2px solid var(--main, #027be3);
}

.ls-cc-btn:hover {
  opacity: 0.88;
}

.ls-cc-panel {
  position: fixed;
  inset: 0;
  background: rgba(0, 0, 0, 0.6);
  z-index: 10000;
  display: flex;
  align-items: flex-end;
  justify-content: center;
}

.ls-cc-panel-hidden {
  display: none;
}

.ls-cc-panel-inner {
  background: #1a1a2e;
  color: #fff;
  width: 100%;
  max-width: 600px;
  max-height: 90vh;
  overflow-y: auto;
  border-radius: 12px 12px 0 0;
  padding: 24px;
}

.ls-cc-panel-title {
  font-size: 18px;
  font-weight: 700;
  margin-bottom: 20px;
}

.ls-cc-category {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 16px;
  padding: 14px 0;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.ls-cc-category-info {
  flex: 1;
}

.ls-cc-category-name {
  font-size: 14px;
  font-weight: 600;
  margin-bottom: 4px;
}

.ls-cc-category-desc {
  font-size: 13px;
  color: rgba(255, 255, 255, 0.65);
  line-height: 1.4;
}

.ls-cc-always-on {
  font-size: 12px;
  color: var(--main, #027be3);
  white-space: nowrap;
  padding-top: 2px;
  font-weight: 600;
}

.ls-cc-toggle {
  position: relative;
  display: inline-block;
  width: 44px;
  height: 24px;
  flex-shrink: 0;
  cursor: pointer;
}

.ls-cc-toggle input {
  opacity: 0;
  width: 0;
  height: 0;
}

.ls-cc-toggle-track {
  position: absolute;
  inset: 0;
  background: rgba(255, 255, 255, 0.2);
  border-radius: 24px;
  transition: 0.2s;
}

.ls-cc-toggle input:checked + .ls-cc-toggle-track {
  background: var(--main, #027be3);
}

.ls-cc-toggle-track::before {
  content: '';
  position: absolute;
  width: 18px;
  height: 18px;
  left: 3px;
  bottom: 3px;
  background: #fff;
  border-radius: 50%;
  transition: 0.2s;
}

.ls-cc-toggle input:checked + .ls-cc-toggle-track::before {
  transform: translateX(20px);
}

.ls-cc-panel-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
  margin-top: 20px;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
  padding-top: 16px;
}

.ls-cc-cookie-details {
  margin-top: 8px;
}

.ls-cc-cookie-summary {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.45);
  cursor: pointer;
  list-style: none;
  padding: 2px 0;
  user-select: none;
}

.ls-cc-cookie-summary::-webkit-details-marker {
  display: none;
}

.ls-cc-cookie-summary::marker {
  display: none;
}

.ls-cc-cookie-table {
  width: 100%;
  border-collapse: collapse;
  margin-top: 6px;
  font-size: 11px;
}

.ls-cc-cookie-table th {
  text-align: left;
  color: rgba(255, 255, 255, 0.4);
  font-weight: 500;
  padding: 3px 6px 3px 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.08);
}

.ls-cc-cookie-table td {
  padding: 5px 6px 5px 0;
  color: rgba(255, 255, 255, 0.7);
  border-bottom: 1px solid rgba(255, 255, 255, 0.04);
  vertical-align: top;
  line-height: 1.3;
}

.ls-cc-cookie-table tr:last-child td {
  border-bottom: none;
}

.ls-cc-no-cookies {
  font-size: 11px;
  color: rgba(255, 255, 255, 0.4);
  margin: 6px 0 0;
}

@media (max-width: 600px) {
  .ls-cc-banner {
    flex-direction: column;
    text-align: center;
    padding: 16px;
  }
  .ls-cc-text {
    min-width: auto;
  }
  .ls-cc-actions {
    width: 100%;
    justify-content: center;
  }
  .ls-cc-btn {
    width: 100%;
  }
  .ls-cc-panel-inner {
    border-radius: 0;
    max-height: 100vh;
  }
}
