/* ============================================================================
   IKSHAA — VILLA TOUR
   Separate sheet so the browser can cache it independently of the markup and
   parse it in parallel with the HTML. Kept deliberately small: this whole
   file is render-blocking, so every rule in it delays first paint.
   ========================================================================= */

:root {
  --ink: #14110e;
  --cream: #efe6d6;
  --serif: "Cormorant Garamond", Georgia, "Times New Roman", serif;
  --sans: "Jost", system-ui, -apple-system, "Segoe UI", sans-serif;

  --rail-w: 2px;
  --bar-h: 64px;
}

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
}

html {
  /* The engine eases scrolling itself; native smooth scrolling on top of
     that fights it and produces a rubbery double-glide. */
  scroll-behavior: auto;
  background: var(--ink);

  /* Scroll is the whole interface here, so the bar has to stay — a hidden
     one would remove the only clue that the page is 29,000px tall. Thinned
     to a hairline instead. Firefox and the standard. */
  scrollbar-width: thin;
  scrollbar-color: rgba(255, 255, 255, 0.26) transparent;
}

/* Chromium and Safari. The two syntaxes never apply in the same browser,
   so they cannot fight; each engine ignores the other's. */
::-webkit-scrollbar {
  width: 6px;
  height: 6px;
}

::-webkit-scrollbar-track {
  background: transparent;
}

::-webkit-scrollbar-thumb {
  background: rgba(255, 255, 255, 0.26);
  border-radius: 999px;
}

::-webkit-scrollbar-thumb:hover {
  background: rgba(255, 255, 255, 0.45);
}

/* Stops the corner square showing as a pale block against the dark stage */
::-webkit-scrollbar-corner {
  background: transparent;
}

body {
  background: var(--ink);
  color: #fff;
  font-family: var(--sans);
  /* height is set by JS from the room count — the page is only tall enough
     to scroll the tour, nothing is laid out down there. */
  overscroll-behavior-y: none;
}

img {
  display: block;
  max-width: 100%;
}

/* ---------- stage ----------
   Fixed and full-bleed. Every room is a layer inside it; only opacity and
   transform ever change, so the browser can keep them on the compositor
   and never re-layout. */
#stage {
  position: fixed;
  inset: 0;
  overflow: hidden;
  z-index: 0;
  background: var(--ink);
}

.room {
  position: absolute;
  inset: 0;
  opacity: 0;
  /* Out-of-window rooms are hidden outright, not merely transparent. A
     transparent layer still composites; a hidden one costs nothing. This is
     the difference between 35 smooth photos and 35 stutters. */
  visibility: hidden;
  will-change: opacity, transform;
}

.room__img {
  width: 100%;
  height: 100%;
  object-fit: cover;

  /* Every source is 1200x800 or 1440x960 — at most 1.4MP. Filling a 1920
     screen already upscales them ~1.6x before any camera move, which is
     the real cause of the softness. Two things compensate: the zoom range
     in the JS is now small, and this slight grade puts back the local
     contrast and saturation that upscaling flattens. */
  filter: saturate(1.06) contrast(1.05);
}

/* On a phone in portrait, `cover` throws away up to 69% of a 3:2 photo to
   fill a 9:19 viewport. `contain` shows the whole frame instead, letterboxed
   against the stage. The JS keeps the camera scale at or below 1 in this
   mode so the movement never crops it back. */
body.isContain .room__img {
  object-fit: contain;
}

/* Film grain, laid over everything.
   This is the actual fix for soft photographs: fine noise gives the eye
   high-frequency detail to hold, so an upscaled image reads as filmic
   rather than blurry. Generated as an inline SVG turbulence — no request,
   no image file, and it scales to any viewport. */
#grain {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  opacity: 0.16;
  mix-blend-mode: overlay;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='3' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='160' height='160' filter='url(%23n)' opacity='0.55'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}

/* A vignette pulls the eye to the middle and away from the softest part of
   an upscaled frame, which is always the edges. */
#vignette {
  position: fixed;
  inset: 0;
  z-index: 2;
  pointer-events: none;
  background: radial-gradient(ellipse at center, rgba(0, 0, 0, 0) 42%, rgba(0, 0, 0, 0.42) 100%);
}

/* A soft vignette so white type holds up over any photograph */
#scrim {
  position: fixed;
  inset: 0;
  z-index: 1;
  pointer-events: none;
  background:
    linear-gradient(to top, rgba(0, 0, 0, 0.72) 0%, rgba(0, 0, 0, 0.1) 46%, rgba(0, 0, 0, 0.28) 100%),
    linear-gradient(to right, rgba(0, 0, 0, 0.5) 0%, rgba(0, 0, 0, 0) 55%);
}

/* ---------- way back ----------
   Top-left, out of the way of the caption which sits bottom-left and of
   the rail on the right. Blurred glass, like every other control here. */
.tourHome {
    position: fixed;
    top: clamp(14px, 2.4vw, 26px);
    left: clamp(14px, 2.4vw, 26px);
    z-index: 4;

    display: flex;
    align-items: center;
    gap: 7px;
    padding: 0.6em 1.05em 0.6em 0.8em;
    border-radius: 999px;

    background-color: rgba(20, 17, 14, 0.42);
    -webkit-backdrop-filter: blur(14px) saturate(150%);
    backdrop-filter: blur(14px) saturate(150%);

    color: #fff;
    font-size: 0.74rem;
    letter-spacing: 0.18em;
    text-transform: uppercase;
    text-decoration: none;
    white-space: nowrap;

    transition: background-color 0.3s ease;
}

.tourHome:hover {
    background-color: rgba(20, 17, 14, 0.68);
}

.tourHome svg {
    width: 15px;
    height: 15px;
    flex: none;
}

/* The intro card covers the whole screen, so until it is dismissed this
   link would sit on top of it and read as part of the card.

   Driven by a body class rather than a sibling selector: .tourHome appears
   BEFORE the cards in the markup, so `~` could never match, and :has()
   would work but makes the rule depend on the DOM order staying put. */
.tourHome {
    opacity: 0;
    pointer-events: none;
    transition: background-color 0.3s ease, opacity 0.5s ease;
}

body.tourStarted .tourHome {
    opacity: 1;
    pointer-events: auto;
}

/* Only the word goes on a phone; the chevron still says "back" */
@media (max-width: 560px) {
    .tourHome span {
        display: none;
    }

    .tourHome {
        padding: 0.6em;
    }
}

/* ---------- caption ---------- */
#caption {
  position: fixed;
  /* above grain and vignette (z 2) */
  left: clamp(20px, 5vw, 76px);
  bottom: calc(var(--bar-h) + clamp(28px, 5vh, 62px));
  z-index: 3;
  max-width: min(30em, 76vw);
  pointer-events: none;
}

.cap__kicker {
  font-size: 0.86rem;
  letter-spacing: 0.3em;
  text-indent: 0.3em;
  text-transform: uppercase;
  opacity: 0.92;
  margin-bottom: 0.9em;
}

.cap__title {
  font-family: var(--serif);
  font-size: clamp(2.4rem, 6.5vw, 4.7rem);
  font-weight: 300;
  line-height: 1.04;
  margin-bottom: 0.35em;
}

.cap__note {
  font-size: clamp(1.08rem, 1.7vw, 1.2rem);
  font-weight: 300;
  line-height: 1.7;
  opacity: 0.96;
  max-width: 34ch;
}

/* Each caption swaps in with a small rise. Transform + opacity only. */
.cap__in {
  animation: capIn 0.7s cubic-bezier(0.16, 1, 0.3, 1) both;
}

@keyframes capIn {
  from {
    opacity: 0;
    transform: translateY(14px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

/* ---------- right-hand rail ---------- */
#rail {
  position: fixed;
  top: 50%;
  right: clamp(14px, 2.4vw, 30px);
  transform: translateY(-50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 14px;
}

#dots {
  display: flex;
  flex-direction: column;
  gap: 9px;
  align-items: center;
}

.railBtn {
  width: 32px;
  height: 32px;
}

.railBtn .ico {
  width: 17px;
  height: 17px;
}

/* At either end of the tour the button has nowhere to go. Dimmed and
   inert rather than removed, so the rail does not change height. */
.railBtn[disabled] {
  opacity: 0.28;
  cursor: default;
  pointer-events: none;
}

/* The tour is driving; manual stepping would only fight it. */
body.isAuto .railBtn {
  opacity: 0;
  pointer-events: none;
}

.rail__dot {
  width: 7px;
  height: 7px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.34);
  cursor: pointer;
  transition: background-color 0.3s ease, transform 0.3s ease;
}

.rail__dot[aria-current="true"] {
  background: #fff;
  transform: scale(1.5);
}

/* ---------- chapter ticks ---------- */
#ticks {
  position: fixed;
  left: clamp(20px, 5vw, 76px);
  top: 50%;
  transform: translateY(-50%);
  z-index: 3;
  display: flex;
  flex-direction: column;
  gap: 14px;
}

.tick {
  display: flex;
  align-items: center;
  gap: 10px;
  background: none;
  border: none;
  padding: 0;
  color: #fff;
  font-family: var(--sans);
  font-size: 0.74rem;
  letter-spacing: 0.22em;
  text-transform: uppercase;
  cursor: pointer;
  opacity: 0.6;
  transition: opacity 0.3s ease;
}

.tick::before {
  content: "";
  width: 16px;
  height: 1px;
  background: currentColor;
  transition: width 0.3s ease;
}

.tick[aria-current="true"] {
  opacity: 1;
}

.tick[aria-current="true"]::before {
  width: 30px;
}

/* ---------- bottom bar ---------- */
#bar {
  position: fixed;
  left: 0;
  bottom: 0;
  z-index: 3;
  width: 100%;
  height: var(--bar-h);
  display: flex;
  align-items: center;
  gap: clamp(14px, 2.4vw, 26px);
  padding: 0 clamp(20px, 5vw, 76px);
}

#counter {
  font-size: 0.82rem;
  letter-spacing: 0.2em;
  opacity: 0.92;
  font-variant-numeric: tabular-nums; /* stops the width jittering per frame */
}

#track {
  flex: 1;
  height: 1px;
  background: rgba(255, 255, 255, 0.24);
}

#fill {
  height: 100%;
  width: 0;
  background: #fff;
  transform-origin: left center;
}

#autoBtn {
  background: rgba(255, 255, 255, 0.16);
  border: none;
  border-radius: 6px;
  padding: 0.7em 1.3em;
  color: #fff;
  font-family: var(--sans);
  font-size: 0.74rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

#autoBtn:hover {
  background: rgba(255, 255, 255, 0.24);
}

#autoBtn[aria-pressed="true"] {
  background: #fff;
  color: var(--ink);
}

/* ---------- icon button ----------
   Transparent with a blur rather than a solid fill, so it reads as glass
   over the photograph instead of a chip sitting on top of it. */
.iconBtn {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 40px;
  height: 40px;
  padding: 0;
  border: none;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.1);
  -webkit-backdrop-filter: blur(14px) saturate(150%);
  backdrop-filter: blur(14px) saturate(150%);
  cursor: pointer;

  /* Both glyphs inherit this, so one property recolours the whole button.
     The JS flips it when the frame behind is too pale for white. */
  color: #fff;
  transition: background-color 0.3s ease, color 0.35s ease;
}

.iconBtn:hover {
  background: rgba(255, 255, 255, 0.22);
}

.ico {
  width: 21px;
  height: 21px;
  /* Survives a bright frame even before the colour flip lands, and stops
     the thin stroke work disappearing into a busy photograph. */
  filter: drop-shadow(0 1px 3px rgba(0, 0, 0, 0.55));
}

/* One glyph at a time, driven by the same attribute screen readers use */
#soundBtn[aria-pressed="false"] .ico--on,
#soundBtn[aria-pressed="true"] .ico--off {
  display: none;
}

/* Set when the frame behind the bar is measured as pale. Inverts the whole
   button: ink glyph on a light pane, with the shadow removed since a dark
   shadow under a dark glyph only muddies it. */
.iconBtn.onLight {
  background: rgba(20, 17, 14, 0.14);
  color: var(--ink);
}

.iconBtn.onLight:hover {
  background: rgba(20, 17, 14, 0.26);
}

.iconBtn.onLight .ico {
  filter: none;
}

/* ---------- intro / outro cards ---------- */
.card {
  position: fixed;
  inset: 0;
  z-index: 5;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: clamp(24px, 6vw, 80px);
  background: rgba(20, 17, 14, 0.82);
  -webkit-backdrop-filter: blur(6px);
  backdrop-filter: blur(6px);
  transition: opacity 0.6s ease, visibility 0.6s ease;
}

.card--gone {
  opacity: 0;
  visibility: hidden;
}

.card__kicker {
  font-size: 0.7rem;
  letter-spacing: 0.32em;
  text-indent: 0.32em;
  text-transform: uppercase;
  opacity: 0.7;
  margin-bottom: 1.4em;
}

.card__title {
  font-family: var(--serif);
  font-size: clamp(3rem, 12vw, 7rem);
  font-weight: 300;
  letter-spacing: 0.12em;
  text-indent: 0.12em;
  line-height: 1;
  margin-bottom: 0.35em;
}

.card__sub {
  max-width: 42ch;
  font-size: clamp(0.92rem, 1.5vw, 1.05rem);
  font-weight: 300;
  line-height: 1.75;
  opacity: 0.82;
  margin-bottom: 2.2em;
}

.card__btn {
  background: #fff;
  color: var(--ink);
  border: none;
  border-radius: 6px;
  padding: 1.05em 2.4em;
  font-family: var(--sans);
  font-size: 0.68rem;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  cursor: pointer;
  transition: opacity 0.3s ease;
}

.card__btn:hover {
  opacity: 0.82;
}

.card__row {
  display: flex;
  flex-wrap: wrap;
  gap: 12px;
  justify-content: center;
}

/* The secondary action, so 'Walk it again' stays the obvious one */
.card__btn--ghost {
  background: transparent;
  color: #fff;
  box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.45);
}

.card__btn--ghost:hover {
  background: rgba(255, 255, 255, 0.14);
  opacity: 1;
}

.card__hint {
  margin-top: 2em;
  font-size: 0.62rem;
  letter-spacing: 0.24em;
  text-transform: uppercase;
  opacity: 0.5;
}

/* #outro only exists once the tour is finished */
#outro {
  opacity: 0;
  visibility: hidden;
}

#outro.card--on {
  opacity: 1;
  visibility: visible;
}

/* ---------- focus ---------- */
:where(button):focus-visible {
  outline: 2px solid #fff;
  outline-offset: 3px;
}

/* ---------- small screens ----------
   The ticks are the first thing to go: they need horizontal room the
   caption also wants, and the rail already gives position. */
/* ---------- small screens ----------
   Two things genuinely overflow before this point, both measured rather
   than guessed:

   1. The rail is 643px tall with 35 dots (551px of dots alone). That
      overflows an iPhone SE 1st gen outright and leaves 24px on an SE 2/3,
      where it collides with the caption and the bar.
   2. The bottom bar leaves the progress track 43px at 375px wide.
   ------------------------------------------------------------------ */

/* The chapter ticks want horizontal room the caption also needs, and the
   rail already gives position. First to go. */
@media (max-width: 820px) {
  #ticks {
    display: none;
  }

  #caption {
    max-width: none;
    /* Stops short of the rail rather than running under it */
    right: calc(clamp(14px, 2.4vw, 30px) + 52px);
  }
}

@media (max-width: 640px) {
  /* The dots are the whole overflow. 35 of them at 7px are also far below
     a tappable target on a phone, so nothing usable is lost — the counter
     and the progress track still give position. */
  #dots {
    display: none;
  }

  /* What remains is prev/next, which move to a horizontal pair sitting
     just above the bar. The right-hand column is then completely free. */
  #rail {
    top: auto;
    bottom: calc(var(--bar-h) + 10px);
    right: clamp(14px, 4vw, 20px);
    transform: none;
    flex-direction: row;
    gap: 10px;
  }

  #caption {
    /* clears the rail row now sitting above the bar */
    bottom: calc(var(--bar-h) + 62px);
    right: clamp(20px, 5vw, 76px);
  }

  /* Reclaim the bar so the track is not squeezed to nothing */
  #bar {
    gap: 10px;
    padding: 0 clamp(14px, 4vw, 20px);
    /* iOS home indicator sits over a bottom-anchored bar otherwise */
    height: calc(var(--bar-h) + env(safe-area-inset-bottom, 0px));
    padding-bottom: env(safe-area-inset-bottom, 0px);
  }

  #counter {
    font-size: 0.7rem;
    letter-spacing: 0.1em;
  }

  #autoBtn {
    font-size: 0.62rem;
    letter-spacing: 0.12em;
    padding: 0.7em 0.95em;
  }

  .iconBtn {
    width: 36px;
    height: 36px;
  }

  .railBtn {
    width: 34px;
    height: 34px;
  }
}

/* Landscape on a phone: almost no height at all, so the caption has to
   give up its note or it runs into the bar. */
@media (max-height: 480px) {
  .cap__note {
    display: none;
  }

  .cap__title {
    font-size: clamp(1.6rem, 5vw, 2.2rem);
  }

  #caption {
    bottom: calc(var(--bar-h) + 16px);
  }

  #rail {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .cap__in {
    animation: none;
  }

  .room {
    transition: none;
  }
}
