/* ==========================================================================
   MAIN  —  D&J GROUP LLC  ·  NAIL SALON TEMPLATE 08  ·  "AURORA"
   --------------------------------------------------------------------------
   Layout and components. There is NOT ONE HEX VALUE in this file — every
   colour comes from tokens.css. If you find yourself typing a `#`, add a
   token instead; that is the whole rebranding contract.

   Order of the file:
       1  reset + base
       2  the mesh (the moving background)
       3  the glass material
       4  type
       5  buttons, pills, links, focus
       6  header + nav
       7  hero
       8  the orbit service selector      <- the signature component
       9  pricing
      10  gallery
      11  reviews carousel
      12  visit / hours / contact
      13  CTA band + page headers
      14  footer + mobile call bar
      15  reveal animation + reduced motion
   ========================================================================== */

/* ═══════════════════════════════════════════════════════════ 1 · BASE ══ */

*, *::before, *::after { box-sizing: border-box; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
}

body {
  margin: 0;
  min-height: 100%;
  background: var(--c-canvas-base);
  color: var(--c-ink);
  font-family: var(--font-body);
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

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

h1, h2, h3, h4 { margin: 0; }
p, ul, ol, figure, blockquote, table { margin: 0; }
ul, ol { padding: 0; list-style: none; }

a { color: var(--c-accent); text-decoration: none; }
a:hover { color: var(--c-ink); }

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

.wrap {
  width: 100%;
  max-width: var(--wrap);
  margin-inline: auto;
  padding-inline: var(--gutter);
}
.wrap--wide   { max-width: var(--wrap-wide); }
.wrap--narrow { max-width: var(--wrap-narrow); }

.section { padding-block: var(--section-y); position: relative; }
.section--tight { padding-block: clamp(3rem, 6vw, 5rem); }
.section--flush-t { padding-top: 0; }

.skip-link {
  position: fixed;
  top: 0; left: 50%;
  transform: translate(-50%, -120%);
  z-index: 200;
  padding: .8rem 1.4rem;
  border-radius: 0 0 var(--r-sm) var(--r-sm);
  background: var(--c-accent);
  color: var(--c-on-accent);
  font-weight: 600;
}
.skip-link:focus-visible { transform: translate(-50%, 0); }

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

/* ════════════════════════════════════════════════════════════ 2 · MESH ══

   Three big blurred orbs on a fixed layer behind everything, plus the veil
   that caps how bright the page can get. The veil is not decoration — see
   the note at the top of tokens.css. Removing it breaks WCAG.

   The orbs are `position: fixed` so the aurora stays put while the content
   scrolls over it. That is cheaper than parallax and it is what makes the
   glass panels read as sliding over a light source rather than carrying
   their own.                                                              */

.mesh {
  position: fixed;
  inset: 0;
  z-index: -1;
  overflow: hidden;
  pointer-events: none;
}

.orb {
  position: absolute;
  border-radius: 50%;
  filter: blur(90px);
  will-change: transform;
  /* --px / --py are nudged by the pointer parallax in main.js. They default
     to zero so the layout is correct with JS switched off. */
  --px: 0px; --py: 0px;
}

.orb--a {
  width: 700px; height: 700px;
  top: -180px; left: -140px;
  background: radial-gradient(circle at 50% 50%, var(--orb-a) 0%, transparent 70%);
  animation: drift-a 26s var(--ease) infinite alternate;
}
.orb--b {
  width: 620px; height: 620px;
  top: 22%; right: -180px;
  background: radial-gradient(circle at 50% 50%, var(--orb-b) 0%, transparent 70%);
  animation: drift-b 31s var(--ease) infinite alternate;
}
.orb--c {
  width: 480px; height: 480px;
  bottom: -120px; left: 38%;
  background: radial-gradient(circle at 50% 50%, var(--orb-c) 0%, transparent 70%);
  animation: drift-c 23s var(--ease) infinite alternate;
}

/* The veil sits above the orbs, below the content. */
.mesh::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--mesh-veil);
}

@keyframes drift-a {
  from { transform: translate3d(var(--px), var(--py), 0) scale(1); }
  to   { transform: translate3d(calc(var(--px) + 120px), calc(var(--py) + 80px), 0) scale(1.15); }
}
@keyframes drift-b {
  from { transform: translate3d(var(--px), var(--py), 0) scale(1.1); }
  to   { transform: translate3d(calc(var(--px) - 140px), calc(var(--py) + 110px), 0) scale(.92); }
}
@keyframes drift-c {
  from { transform: translate3d(var(--px), var(--py), 0) scale(.95); }
  to   { transform: translate3d(calc(var(--px) + 90px), calc(var(--py) - 70px), 0) scale(1.2); }
}

/* The mesh brightens toward the bottom of the page, per the brief. A second
   fixed layer rather than a footer background, so the glow bleeds up over
   the last two sections instead of stopping at a hard edge. */
.mesh__foot {
  position: absolute;
  inset: 0;
  background: var(--mesh-foot);
}

/* ═══════════════════════════════════════════════════════════ 3 · GLASS ══

   One material, used everywhere. `.glass` gives you the frosted panel;
   `.glass--lit` is the hover/active state.

   About the scrim: --glass-scrim is painted as a ::before INSIDE the panel,
   under the content but over the blurred backdrop. This is the accessibility
   fix the brief calls for. Without it the contrast of text in a panel
   depends on where the orbs happen to be, which is not a thing you can test.
   With it, every panel presents its text on a known background.           */

.glass {
  position: relative;
  border-radius: var(--r);
  background: var(--glass-fill);
  border: 1px solid var(--c-line);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  box-shadow: var(--shadow);
  isolation: isolate;
}
.glass > * { position: relative; z-index: 1; }

.glass::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 0;
  border-radius: inherit;
  background: var(--glass-scrim);
  pointer-events: none;
}

/* A one-pixel highlight along the top edge. This is the detail that stops a
   translucent rectangle from looking like a translucent rectangle: real
   glass catches light on its top bevel. */
.glass::after {
  content: "";
  position: absolute;
  inset: 0 0 auto 0;
  height: 1px;
  z-index: 2;
  border-radius: inherit;
  background: linear-gradient(90deg,
                transparent 0%,
                var(--c-line-strong) 20%,
                var(--c-line-strong) 80%,
                transparent 100%);
  pointer-events: none;
}

.glass--lit {
  background: var(--glass-fill-2);
  backdrop-filter: var(--glass-blur-lg);
  -webkit-backdrop-filter: var(--glass-blur-lg);
}

/* Browsers without backdrop-filter (older Firefox with the flag off, some
   embedded webviews) get a flat panel instead of a blurred one. Slightly
   more opaque, because there is no blur doing the separating. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass { background: var(--glass-fill-2); }
  .glass::before { background: var(--glass-scrim-solid); }
}

/* ════════════════════════════════════════════════════════════ 4 · TYPE ══ */

h1, h2, h3, .h-display {
  font-family: var(--font-display);
  font-weight: 300;
  line-height: var(--lh-tight);
  letter-spacing: var(--ls-h);
  color: var(--c-ink);
}

.h-display {
  font-size: var(--fs-display);
  line-height: var(--lh-display);
  letter-spacing: var(--ls-display);
}
h1 { font-size: var(--fs-h1); }
h2 { font-size: var(--fs-h2); }
h3 { font-size: var(--fs-h3); font-weight: 600; letter-spacing: 0; }

/* Sora 600 inside a 300 headline. The brief asks for exactly two weights, so
   emphasis is carried by weight and colour rather than by a second family. */
.hl { font-weight: 600; color: var(--c-accent); }

.label {
  display: block;
  margin: 0 0 1rem;
  font-family: var(--font-body);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-accent);
}
.label--center { text-align: center; }

.lede {
  font-size: var(--fs-lede);
  line-height: 1.62;
  color: var(--c-ink-soft);
}
.fine  { font-size: var(--fs-fine); color: var(--c-mute); line-height: 1.6; }
.small { font-size: var(--fs-small); color: var(--c-ink-soft); }
.mute  { color: var(--c-mute); }

.prose p + p { margin-top: 1.15rem; }
.prose p { color: var(--c-ink-soft); }

/* Section head: eyebrow + heading, used at the top of most sections. */
.shead { max-width: 58ch; margin-bottom: clamp(2rem, 4vw, 3.25rem); }
.shead--center { margin-inline: auto; text-align: center; }
.shead p:not(.label) { margin-top: 1rem; }

/* Numerals line up in every price column. */
.num { font-variant-numeric: tabular-nums; }

/* A warning ribbon, kept for the handover rather than used on the page.
   The site is written to read as a finished business, so nothing is marked
   up as provisional in front of a visitor; what is invented is recorded in
   the HTML comments and in CLIENT-SETUP.md §7 instead. Add this class to
   anything you do want flagged while a client is still reviewing copy —
   an unconfirmed price, a policy nobody has signed off. */
.warning-tag {
  display: inline-block;
  margin-bottom: .9rem;
  padding: .28rem .7rem;
  border: 1px solid var(--c-warn);
  border-radius: var(--r-pill);
  background: var(--c-warn-wash);
  color: var(--c-warn);
  font-size: var(--fs-label);
  font-weight: 500;
  letter-spacing: .08em;
  text-transform: uppercase;
}

/* ════════════════════════════════════════════════ 5 · CONTROLS + FOCUS ══ */

.btn {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  padding: .82rem 1.5rem;
  border: 1px solid transparent;
  border-radius: var(--r-pill);
  font-family: var(--font-body);
  font-size: var(--fs-small);
  font-weight: 500;
  letter-spacing: .01em;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease),
              transform var(--dur-fast) var(--ease),
              box-shadow var(--dur-fast) var(--ease);
}
.btn svg { width: 1.05em; height: 1.05em; flex: none; }

.btn--primary {
  background: var(--c-accent);
  color: var(--c-on-accent);
  box-shadow: var(--glow-soft);
}
.btn--primary:hover { background: var(--c-accent-dp); color: var(--c-on-accent); transform: translateY(-2px); }

/* Glass button. Its border is --c-line-strong, not --c-line: it is a control,
   so its edge has to clear 3:1. */
.btn--glass {
  background: var(--glass-fill);
  border-color: var(--c-line-strong);
  color: var(--c-ink);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}
.btn--glass:hover { background: var(--glass-fill-2); color: var(--c-ink); transform: translateY(-2px); }

.btn--ghost { color: var(--c-accent); padding-inline: .4rem; }
.btn--ghost:hover { color: var(--c-ink); }

.btn--lg { padding: 1rem 1.85rem; font-size: var(--fs-body); }
.btn--sm { padding: .6rem 1.1rem; font-size: var(--fs-fine); }

.btn-row { display: flex; flex-wrap: wrap; gap: .8rem; align-items: center; }
.btn-row--center { justify-content: center; }

/* One focus treatment for the whole site. Accent measures 10.3:1 on the navy,
   6.6:1 on the brightest mesh and 7.7:1 on glass, so a single ring colour is
   safe everywhere; the dark outer ring is what keeps it visible when the
   control happens to sit on a pale photograph. */
:focus-visible {
  outline: 2px solid var(--c-focus);
  outline-offset: 3px;
  border-radius: var(--r-sm);
  box-shadow: 0 0 0 5px var(--c-focus-out);
}

/* ══════════════════════════════════════════════════════════ 6 · HEADER ══

   A glass bar floating --head-gap from the top, full width minus the gutter.
   It does not change size on scroll — it gains a shadow, which is enough of
   a signal on a page this dark.                                            */

.site-head {
  position: fixed;
  top: var(--head-gap);
  left: 0; right: 0;
  z-index: 100;
  padding-inline: var(--gutter);
}

.site-head__in {
  display: flex;
  align-items: center;
  gap: 1rem;
  height: var(--head-h);
  max-width: var(--wrap-wide);
  margin-inline: auto;
  padding-inline: clamp(.9rem, 2vw, 1.4rem);
  border-radius: var(--r-pill);
}
.site-head.is-stuck .site-head__in { box-shadow: var(--shadow-lg); }

/* The logo, in its glass chip. The mark is the white knockout
   (logo-white.png) and --glow-logo is what sells the idea that it is lit
   from behind the panel rather than printed on it. */
.brandmark { display: inline-flex; align-items: center; gap: .7rem; color: var(--c-ink); }
.brandmark:hover { color: var(--c-ink); }

.glasschip {
  display: grid;
  place-items: center;
  width: 46px; height: 46px;
  flex: none;
  border-radius: 14px;
  background: var(--glass-fill-2);
  border: 1px solid var(--c-line);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  overflow: hidden;
}
.glasschip img {
  width: 30px; height: auto;
  filter: var(--glow-logo);
}
.glasschip--lg { width: 76px; height: 76px; border-radius: 20px; }
.glasschip--lg img { width: 50px; }

.brandmark__text { display: flex; flex-direction: column; line-height: 1.15; }
.brandmark__name {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.02rem;
  letter-spacing: -.01em;
}
.brandmark__tag {
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-mute);
}

.mainnav {
  display: flex;
  align-items: center;
  gap: clamp(.4rem, 1.4vw, 1.1rem);
  margin-left: auto;
}

.navlink {
  padding: .5rem .7rem;
  border-radius: var(--r-pill);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
  font-weight: 500;
  transition: color var(--dur-fast) var(--ease), background var(--dur-fast) var(--ease);
}
.navlink:hover { color: var(--c-ink); background: var(--glass-fill); }
.navlink[aria-current="page"] { color: var(--c-accent); background: var(--glass-fill); }

.head-phone {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .5rem .8rem;
  border-radius: var(--r-pill);
  color: var(--c-ink);
  font-size: var(--fs-small);
  font-weight: 500;
  font-variant-numeric: tabular-nums;
}
.head-phone svg { width: 1em; height: 1em; color: var(--c-accent); }
.head-phone:hover { background: var(--glass-fill); color: var(--c-ink); }

/* Live open/closed pill. main.js fills the text from data-schedule. */
.status {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
  padding: .42rem .8rem;
  border: 1px solid var(--c-line);
  border-radius: var(--r-pill);
  background: var(--glass-fill);
  font-size: var(--fs-fine);
  color: var(--c-ink-soft);
  white-space: nowrap;
}
.status__dot {
  width: 7px; height: 7px;
  border-radius: 50%;
  background: var(--c-mute);
  flex: none;
}
.status.is-open  .status__dot { background: var(--c-open); box-shadow: 0 0 0 3px var(--c-open-wash); }
.status.is-closed .status__dot { background: var(--c-closed); }
.status.is-open  .status__text { color: var(--c-open); }
.status.is-closed .status__text { color: var(--c-closed); }

.navtoggle {
  display: none;
  width: 44px; height: 44px;
  margin-left: auto;
  padding: 0;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-pill);
  background: var(--glass-fill);
  cursor: pointer;
}
.navtoggle span {
  display: block;
  width: 18px; height: 1.5px;
  margin: 3.5px auto;
  background: var(--c-ink);
  transition: transform var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease);
}
.navtoggle[aria-expanded="true"] span:nth-child(1) { transform: translateY(5px) rotate(45deg); }
.navtoggle[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.navtoggle[aria-expanded="true"] span:nth-child(3) { transform: translateY(-5px) rotate(-45deg); }

@media (max-width: 1040px) {
  .navtoggle { display: block; }
  .mainnav {
    position: absolute;
    top: calc(100% + 10px);
    left: 0; right: 0;
    flex-direction: column;
    align-items: stretch;
    gap: .25rem;
    padding: 1rem;
    border-radius: var(--r);
    background: var(--c-canvas-base);
    border: 1px solid var(--c-line);
    box-shadow: var(--shadow-lg);
    display: none;
  }
  .mainnav.is-open { display: flex; }
  .navlink { padding: .75rem 1rem; }
  .head-phone, .status { justify-content: center; }
  .mainnav .btn { justify-content: center; }
}

/* Every page pushes its content clear of the floating header. */
main { padding-top: calc(var(--head-h) + var(--head-gap)); }

/* ══════════════════════════════════════════════════════════════ 7 · HERO ══

   Centred type over the mesh, with a glass card of three facts below it and
   two glass-framed photographs flanking. No full-bleed hero image: on this
   design the background IS the hero.                                        */

.hero { padding-block: clamp(3.5rem, 8vw, 7rem) clamp(3rem, 6vw, 5rem); text-align: center; }

.hero__head { max-width: 22ch; margin-inline: auto; }
.hero__lede { max-width: 56ch; margin: 1.4rem auto 0; }
.hero .btn-row { margin-top: 2rem; }

/* Three key facts, in one glass card, per the brief. */
.factcard {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 1px;
  max-width: 760px;
  margin: clamp(2.5rem, 5vw, 3.75rem) auto 0;
  overflow: hidden;
}
.factcard__item { padding: 1.4rem 1.2rem; text-align: center; }
.factcard__item + .factcard__item { box-shadow: -1px 0 0 var(--c-line); }
.factcard__k {
  display: block;
  font-size: var(--fs-label);
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-mute);
}
.factcard__v {
  display: block;
  margin-top: .4rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
  color: var(--c-ink);
}
@media (max-width: 640px) {
  .factcard { grid-template-columns: 1fr; }
  .factcard__item + .factcard__item { box-shadow: 0 -1px 0 var(--c-line); }
}

/* The two floating photographs. They are decorative company for the
   headline, so they are hidden below 900px rather than stacked. */
.hero__floats { position: relative; max-width: var(--wrap); margin-inline: auto; }
.hero__float {
  position: absolute;
  width: clamp(150px, 15vw, 216px);
  animation: bob 9s var(--ease) infinite alternate;
}
.hero__float--l { left: 0;  top: -30px; }
.hero__float--r { right: 0; top: 40px; animation-delay: -4.5s; }
@keyframes bob {
  from { transform: translateY(0); }
  to   { transform: translateY(-18px); }
}
@media (max-width: 1080px) { .hero__float { display: none; } }

/* ═══════════════════════════════════════════════════ 8 · ORBIT SELECTOR ══

   The signature component. Six service labels arranged on a circle around a
   central glass disc; choosing one rotates the ring so that label lands at
   the top, and the disc crossfades to its detail.

   Structure (see index.html / services.html):
       .orbit
         .orbit__ring   role=tablist   — rotates
           .orbit__node role=tab       — counter-rotates so text stays level
         .orbit__disc
           .orbit__panel role=tabpanel — one per service, one visible

   Geometry: each node is placed with the standard
       rotate(θ) translateY(-r) rotate(-θ)
   trick, where θ = i/n turns. The ring then rotates by -active/n turns to
   bring the active node to 12 o'clock, and each node adds the same amount
   back so its text never tilts. All of it is CSS custom properties, so
   main.js only ever sets --active and an aria attribute.

   Below 900px the ring stops being a ring: --orbit-r goes to 0, the nodes
   un-rotate and lay out as a stacked list, and the disc follows underneath.
   That is the fallback the brief asks for, and it is the same DOM and the
   same JS — no second implementation to keep in sync.                       */

.orbit {
  display: grid;
  place-items: center;
  position: relative;
  min-height: calc(var(--orbit-r) * 2 + 190px);
  margin-top: clamp(1rem, 3vw, 2rem);
}

.orbit__ring {
  position: absolute;
  inset: 0;
  display: grid;
  place-items: center;
  /* --n and --i below have CSS defaults ON PURPOSE. main.js sets both, but
     `calc()` containing an undefined custom property is invalid at computed-
     value time, which would throw away the whole transform — so with JS
     blocked the six labels would stack in a pile at the centre of the disc
     instead of sitting on the circle. The defaults make the ring lay out
     correctly on its own; the script then only has to keep them in step if
     the client changes how many services there are. */
  --n: 6;
  transform: rotate(calc(var(--active, 0) * -1turn / var(--n)));
  transition: transform var(--dur) var(--ease-out);
}

.orbit__node {
  position: absolute;
  display: flex;
  align-items: center;
  gap: .6rem;
  padding: .7rem 1.15rem;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-pill);
  background: var(--glass-fill);
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
  font-weight: 500;
  white-space: nowrap;
  cursor: pointer;
  /* place on the circle, then undo both rotations so the label stays level */
  transform:
    rotate(calc(var(--i) * 1turn / var(--n)))
    translateY(calc(var(--orbit-r) * -1))
    rotate(calc(var(--i) * -1turn / var(--n)))
    rotate(calc(var(--active, 0) * 1turn / var(--n)));
  transition: transform var(--dur) var(--ease-out),
              background var(--dur-fast) var(--ease),
              border-color var(--dur-fast) var(--ease),
              color var(--dur-fast) var(--ease);
}

/* Each node's index, from its position in the DOM. Ten is more services than
   any salon should put on one circle, and covers every plausible edit. */
.orbit__node:nth-child(1)  { --i: 0; }
.orbit__node:nth-child(2)  { --i: 1; }
.orbit__node:nth-child(3)  { --i: 2; }
.orbit__node:nth-child(4)  { --i: 3; }
.orbit__node:nth-child(5)  { --i: 4; }
.orbit__node:nth-child(6)  { --i: 5; }
.orbit__node:nth-child(7)  { --i: 6; }
.orbit__node:nth-child(8)  { --i: 7; }
.orbit__node:nth-child(9)  { --i: 8; }
.orbit__node:nth-child(10) { --i: 9; }

.orbit__node:hover { background: var(--glass-fill-2); color: var(--c-ink); }
.orbit__node[aria-selected="true"] {
  background: var(--c-accent);
  border-color: var(--c-accent);
  color: var(--c-on-accent);
  box-shadow: var(--glow-soft);
}
.orbit__num {
  font-size: var(--fs-fine);
  font-variant-numeric: tabular-nums;
  opacity: .75;
}

/* The disc in the middle. */
.orbit__disc {
  position: relative;
  z-index: 1;
  width: var(--orbit-disc);
  height: var(--orbit-disc);
  border-radius: 50%;
  display: grid;
  place-items: center;
  overflow: hidden;
  text-align: center;
}
.orbit__disc.glass { border-radius: 50%; }

.orbit__panel {
  grid-area: 1 / 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: .35rem;
  width: 100%; height: 100%;
  padding: clamp(1.5rem, 3vw, 2.4rem);
  opacity: 0;
  visibility: hidden;
  transform: scale(.97);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease),
              visibility 0s linear var(--dur);
}
.orbit__panel.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease),
              visibility 0s;
}
.orbit__panel h3 { font-family: var(--font-display); font-size: 1.28rem; }
.orbit__panel p  { font-size: var(--fs-fine); color: var(--c-ink-soft); line-height: 1.55; }
.orbit__price {
  margin-top: .3rem;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.5rem;
  color: var(--c-accent);
  font-variant-numeric: tabular-nums;
}
.orbit__meta { font-size: var(--fs-label); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--c-mute); }

/* The photograph behind the disc. Two things hold it back: a low opacity, and
   a radial scrim that is heaviest in the middle — exactly where the type is.
   Without the scrim the description sits on whatever the photo is doing at
   that point, which is the one place on this site where the background under
   text is genuinely unpredictable. */
.orbit__panelbg {
  position: absolute;
  inset: 0;
  z-index: -1;
  opacity: .34;
}
.orbit__panelbg img { width: 100%; height: 100%; object-fit: cover; }
.orbit__panelbg::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--scrim-disc);
}

/* A faint guide circle so the ring reads as an orbit even before you
   interact with it. Decorative only. */
.orbit__path {
  position: absolute;
  width: calc(var(--orbit-r) * 2);
  height: calc(var(--orbit-r) * 2);
  border: 1px dashed var(--c-line);
  border-radius: 50%;
  pointer-events: none;
}

.orbit__hint {
  margin-top: 1.25rem;
  text-align: center;
  font-size: var(--fs-fine);
  color: var(--c-mute);
}

@media (max-width: 900px) {
  /* Ring becomes a stacked list. Same DOM, same JS. */
  .orbit { display: block; min-height: 0; }
  .orbit__ring {
    position: static;
    /* Both of these are load-bearing, and measured rather than assumed:
       without `width: 100%` the ring lays out at its widest label (181px of
       a 350px column) and without `align-items: stretch` the base rule's
       `place-items: center` survives the grid-to-flex switch and shrink-wraps
       each row to its own text. Either one alone leaves six ragged pills
       floating down the middle of a phone screen. */
    width: 100%;
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: .5rem;
    transform: none;
    margin-bottom: 1.5rem;
  }
  .orbit__node {
    position: static;
    transform: none;
    justify-content: flex-start;
    width: 100%;
  }
  .orbit__disc {
    width: 100%;
    height: auto;
    min-height: 280px;
    border-radius: var(--r);
    margin-inline: auto;
  }
  .orbit__disc.glass { border-radius: var(--r); }
  .orbit__panel { position: relative; }
  .orbit__path { display: none; }
}

/* ═══════════════════════════════════════════════════════════ 9 · PRICING ══

   Glass rows on a single card, hairline separated — the brief's shape. No
   per-item cards: the point is that the whole menu reads as one object.     */

.pricecard { overflow: hidden; }

/* Two columns, two rows: name over description on the left, price over
   duration on the right. Rows and columns are assigned explicitly rather than
   auto-flowed, because the home page uses rows WITHOUT a duration and an
   auto-flowed price would drop to the wrong cell there. */
.pricerow {
  display: grid;
  grid-template-columns: 1fr auto;
  gap: .3rem 1.5rem;
  padding: 1.15rem clamp(1.1rem, 2.5vw, 1.9rem);
  transition: background var(--dur-fast) var(--ease);
}
.pricerow + .pricerow { box-shadow: 0 -1px 0 var(--c-line); }
.pricerow:hover { background: var(--glass-fill); }

.pricerow__name {
  grid-column: 1; grid-row: 1;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.02rem;
  color: var(--c-ink);
}
.pricerow__desc { grid-column: 1; grid-row: 2; font-size: var(--fs-fine); color: var(--c-mute); }
.pricerow__price {
  grid-column: 2; grid-row: 1;
  text-align: right;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.25rem;
  color: var(--c-accent);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}
.pricerow__dur {
  grid-column: 2; grid-row: 2;
  text-align: right;
  font-size: var(--fs-fine);
  color: var(--c-mute);
  white-space: nowrap;
}

.pricehead {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1rem;
  align-items: baseline;
  justify-content: space-between;
  padding: 1.1rem clamp(1.1rem, 2.5vw, 1.9rem);
  box-shadow: 0 1px 0 var(--c-line);
  background: var(--glass-fill);
}
.pricehead h3 { font-family: var(--font-display); }

/* --- the services page: one alternating media/copy block per category --- */

.svc { padding-block: clamp(3rem, 6vw, 5rem); }

.svc__grid {
  display: grid;
  grid-template-columns: minmax(0, 5fr) minmax(0, 7fr);
  gap: clamp(1.5rem, 4vw, 3.5rem);
  align-items: center;
}
/* Alternating sides. The media element is first in the DOM on every block,
   because reading order should be image-then-prices regardless of which side
   the image lands on.

   The template flips with it. Moving the media to the second slot with
   `order` alone would leave it in the 7fr column, so every other block would
   have a bigger picture and a narrower price list — which reads as a mistake
   rather than as a rhythm. */
.svc--flip .svc__grid { grid-template-columns: minmax(0, 7fr) minmax(0, 5fr); }
.svc--flip .svc__media { order: 2; }

/* An even split, for the blocks that pair copy with a map or a wide
   photograph rather than with a price list. */
.svc__grid--even { grid-template-columns: minmax(0, 1fr) minmax(0, 1fr); }

.svc__body .pricecard { margin-top: 1.75rem; }
.svc__body .prose { max-width: 52ch; }
.svc__body .ticks { margin-top: 1.25rem; }

@media (max-width: 860px) {
  .svc__grid { grid-template-columns: 1fr; }
  .svc--flip .svc__media { order: 0; }
  .svc__media { max-width: 420px; }
}

/* --- category jump bar, pinned under the page header ------------------- */

.jumpnav {
  display: flex;
  flex-wrap: wrap;
  gap: .35rem;
  padding: .55rem;
  margin-top: calc(var(--section-y) * -0.35);
}
.jumpnav a {
  padding: .55rem .95rem;
  border-radius: var(--r-pill);
  color: var(--c-ink-soft);
  font-size: var(--fs-small);
  font-weight: 500;
  white-space: nowrap;
  transition: background var(--dur-fast) var(--ease), color var(--dur-fast) var(--ease);
}
.jumpnav a:hover { background: var(--glass-fill-2); color: var(--c-ink); }
@media (max-width: 640px) {
  /* A wrapped six-item bar eats half a phone screen. Scroll it instead. */
  .jumpnav { flex-wrap: nowrap; overflow-x: auto; scrollbar-width: none; }
  .jumpnav::-webkit-scrollbar { display: none; }
}

/* Anchor targets have to clear the floating header. */
:target { scroll-margin-top: calc(var(--head-h) + var(--head-gap) + 16px); }
.svc { scroll-margin-top: calc(var(--head-h) + var(--head-gap) + 16px); }

/* --- tick list --------------------------------------------------------- */

.ticks li {
  position: relative;
  padding-left: 1.75rem;
  font-size: var(--fs-small);
  color: var(--c-ink-soft);
  line-height: 1.6;
}
.ticks li + li { margin-top: .85rem; }
.ticks li::before {
  content: "";
  position: absolute;
  left: 0; top: .5em;
  width: 8px; height: 8px;
  border-radius: 50%;
  background: var(--c-accent);
  box-shadow: 0 0 0 4px var(--c-accent-halo);
}

/* ═══════════════════════════════════════════════════════════ 10 · PHOTOS ══

   Every photograph on the site goes through .ph. It applies the cool grade
   and lays the violet-to-aqua wash over it in soft-light, which is what
   makes twenty-six frames from a dozen shoots look like one commission.     */

.ph {
  position: relative;
  overflow: hidden;
  border-radius: var(--r);
  background: var(--glass-fill);
  isolation: isolate;
}
.ph img {
  width: 100%; height: 100%;
  object-fit: cover;
  filter: var(--photo-grade);
  transition: transform var(--dur) var(--ease-out), filter var(--dur) var(--ease);
}
.ph::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: 1;
  background: var(--photo-wash);
  opacity: var(--photo-wash-o);
  mix-blend-mode: soft-light;
  pointer-events: none;
  transition: opacity var(--dur) var(--ease);
}
.ph--1x1 { aspect-ratio: 1 / 1; }
.ph--4x5 { aspect-ratio: 4 / 5; }
.ph--3x2 { aspect-ratio: 3 / 2; }

/* Glass-framed photo. The frame is a glass panel with the picture inset, so
   hovering can lift the frame AND deepen its blur — which is what makes the
   photograph appear to come forward out of the panel. */
.frame {
  padding: 10px;
  border-radius: var(--r-lg);
  transition: transform var(--dur) var(--ease-out),
              background var(--dur) var(--ease),
              box-shadow var(--dur) var(--ease);
}
.frame .ph { border-radius: calc(var(--r-lg) - 10px); }

a.frame:hover, .frame:hover {
  transform: translateY(-8px);
  background: var(--glass-fill-2);
  backdrop-filter: var(--glass-blur-lg);
  -webkit-backdrop-filter: var(--glass-blur-lg);
  box-shadow: var(--shadow-lg), var(--glow-soft);
}
.frame:hover .ph img { transform: scale(1.04); }
.frame:hover .ph::after { opacity: 0; }

.frame__cap {
  display: flex;
  justify-content: space-between;
  gap: 1rem;
  padding: .8rem .5rem .25rem;
  font-size: var(--fs-fine);
  color: var(--c-mute);
}
.frame__cap b { color: var(--c-ink-soft); font-weight: 500; }

/* The 3-column gallery grid. */
.gallery {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 2vw, 1.6rem);
}
@media (max-width: 980px) { .gallery { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .gallery { grid-template-columns: 1fr; } }

/* ══════════════════════════════════════════════════════════ 11 · REVIEWS ══

   One wide glass card, dot-paginated, auto-advancing and pausable. The
   pause button is real, not a hover trick, because an auto-advancing
   carousel that cannot be stopped fails WCAG 2.2.2.                        */

.reviews { position: relative; padding: clamp(2rem, 5vw, 3.25rem) clamp(1.5rem, 5vw, 4rem) 0; text-align: center; }

/* The stage stacks all three reviews in one grid cell so they can crossfade.
   NOTHING ELSE MAY LIVE IN HERE — an extra child gets auto-placed into a
   second row and the card grows a stray full-width band. The honesty ribbon
   sits outside it, above. */
.reviews__stage { display: grid; }
.review {
  grid-area: 1 / 1;
  opacity: 0;
  visibility: hidden;
  transform: translateY(10px);
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease),
              visibility 0s linear var(--dur);
}
.review.is-active {
  opacity: 1;
  visibility: visible;
  transform: none;
  transition: opacity var(--dur) var(--ease), transform var(--dur) var(--ease),
              visibility 0s;
}
.review blockquote {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1.25rem, 2.6vw, 1.85rem);
  line-height: 1.42;
  letter-spacing: var(--ls-h);
  color: var(--c-ink);
  max-width: 40ch;
  margin-inline: auto;
}
.review figcaption { margin-top: 1.25rem; font-size: var(--fs-small); color: var(--c-mute); }
.review__stars { display: flex; justify-content: center; gap: .2rem; margin-bottom: 1.2rem; color: var(--c-accent); }
.review__stars svg { width: 16px; height: 16px; }

.reviews__foot {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-top: clamp(1.75rem, 3vw, 2.5rem);
  padding-bottom: clamp(1.5rem, 3vw, 2rem);
}
.dots { display: flex; gap: .5rem; }
.dot {
  width: 9px; height: 9px;
  padding: 0;
  border: 1px solid var(--c-line-strong);
  border-radius: 50%;
  background: transparent;
  cursor: pointer;
  transition: background var(--dur-fast) var(--ease), transform var(--dur-fast) var(--ease);
}
.dot[aria-selected="true"] { background: var(--c-accent); border-color: var(--c-accent); transform: scale(1.25); }

.pausebtn {
  display: inline-flex;
  align-items: center;
  gap: .4rem;
  padding: .38rem .8rem;
  border: 1px solid var(--c-line-strong);
  border-radius: var(--r-pill);
  background: transparent;
  color: var(--c-mute);
  font-size: var(--fs-fine);
  cursor: pointer;
}
.pausebtn:hover { color: var(--c-ink); background: var(--glass-fill); }
.pausebtn svg { width: 11px; height: 11px; }

/* ════════════════════════════════════════════════════════════ 12 · VISIT ══ */

.duo {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1rem, 2.4vw, 1.75rem);
  align-items: start;
}
@media (max-width: 860px) { .duo { grid-template-columns: 1fr; } }

.card { padding: clamp(1.5rem, 3vw, 2.25rem); }
.card > h3 { margin-bottom: 1.1rem; }

.hours { width: 100%; border-collapse: collapse; }
.hours th, .hours td { padding: .6rem 0; text-align: left; font-weight: 400; font-size: var(--fs-small); }
.hours th { color: var(--c-ink-soft); }
.hours td { text-align: right; color: var(--c-ink); font-variant-numeric: tabular-nums; }
.hours td.closed { color: var(--c-mute); }
.hours tr + tr th, .hours tr + tr td { box-shadow: 0 -1px 0 var(--c-line-soft); }
.hours tr.is-today th { color: var(--c-accent); font-weight: 500; }
.hours tr.is-today td { color: var(--c-accent); }

.contact-list li { display: flex; gap: .9rem; padding: .75rem 0; }
.contact-list li + li { box-shadow: 0 -1px 0 var(--c-line-soft); }
.contact-list .ico {
  display: grid; place-items: center;
  width: 34px; height: 34px;
  flex: none;
  border-radius: var(--r-pill);
  background: var(--glass-fill);
  border: 1px solid var(--c-line);
  color: var(--c-accent);
}
.contact-list .ico svg { width: 15px; height: 15px; }
.contact-list .k { display: block; font-size: var(--fs-label); letter-spacing: var(--ls-label); text-transform: uppercase; color: var(--c-mute); }
.contact-list .v { display: block; margin-top: .15rem; color: var(--c-ink); }
a.v:hover { color: var(--c-accent); }

/* Google's embed is a light map, which on this page looks like a hole cut in
   the design. Inverting and rotating the hue turns it into a dark map that
   sits in the palette; grayscale first so the inversion does not produce
   sickly greens. Delete the filter if the client would rather have the real
   Google colours — nothing else depends on it. */
.mapframe { overflow: hidden; border-radius: var(--r); padding: 8px; }
.mapframe iframe {
  display: block;
  width: 100%; height: 380px;
  border: 0;
  border-radius: calc(var(--r) - 8px);
  filter: grayscale(.3) invert(.92) hue-rotate(180deg) contrast(.9);
}

/* --- FAQ ---------------------------------------------------------------
   Native <details>, so it works with JS off and the browser handles the
   keyboard for us. The only thing CSS adds is the +/- marker. */

.faq__item { padding: 0 clamp(1.1rem, 2.5vw, 1.9rem); }
.faq__item + .faq__item { box-shadow: 0 -1px 0 var(--c-line); }

.faq__item summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1.5rem;
  padding: 1.25rem 0;
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.02rem;
  color: var(--c-ink);
  cursor: pointer;
  list-style: none;
}
.faq__item summary::-webkit-details-marker { display: none; }

/* The marker is a + that becomes a − when open: two bars, one of which is
   rotated away. Drawn in CSS so there is no icon to load or recolour. */
.faq__item summary::after {
  content: "";
  flex: none;
  width: 13px; height: 13px;
  background:
    linear-gradient(var(--c-accent), var(--c-accent)) center / 13px 1.5px no-repeat,
    linear-gradient(var(--c-accent), var(--c-accent)) center / 1.5px 13px no-repeat;
  transition: transform var(--dur-fast) var(--ease), opacity var(--dur-fast) var(--ease);
}
.faq__item[open] summary::after {
  background: linear-gradient(var(--c-accent), var(--c-accent)) center / 13px 1.5px no-repeat;
  transform: rotate(180deg);
}
.faq__item summary:hover { color: var(--c-accent); }

.faq__item p {
  padding-bottom: 1.4rem;
  max-width: 62ch;
  font-size: var(--fs-small);
  color: var(--c-ink-soft);
}

/* ══════════════════════════════════════════════ 13 · BANDS + PAGEHEADS ══ */

.band {
  position: relative;
  overflow: hidden;
  border-radius: var(--r-lg);
  padding: clamp(2.75rem, 6vw, 4.5rem) clamp(1.5rem, 5vw, 4rem);
  text-align: center;
}
.band__media { position: absolute; inset: 0; z-index: 0; }
.band__media img { width: 100%; height: 100%; object-fit: cover; filter: var(--photo-grade); }
.band__media::after { content: ""; position: absolute; inset: 0; background: var(--scrim-photo); }
.band > *:not(.band__media) { position: relative; z-index: 1; }
.band p { max-width: 52ch; margin-inline: auto; color: var(--c-ink-soft); }
.band .btn-row { margin-top: 1.75rem; }

.pagehead {
  position: relative;
  overflow: hidden;
  padding-block: clamp(3.5rem, 8vw, 6rem);
  text-align: center;
}
.pagehead__media { position: absolute; inset: 0; z-index: 0; }
.pagehead__media img { width: 100%; height: 100%; object-fit: cover; filter: var(--photo-grade); }
.pagehead__media::after { content: ""; position: absolute; inset: 0; background: var(--scrim-photo); }
/* The :not() matters. `.pagehead > *` alone has the same specificity as
   `.pagehead__media` and, coming later, would win — turning the absolutely
   positioned backdrop into an ordinary block and pushing the heading out
   from under its own photograph. */
.pagehead > *:not(.pagehead__media) { position: relative; z-index: 1; }
.pagehead p { max-width: 54ch; margin: 1.1rem auto 0; }

.crumb { font-size: var(--fs-fine); color: var(--c-mute); margin-bottom: .8rem; }
.crumb a { color: var(--c-mute); }
.crumb a:hover { color: var(--c-accent); }

/* ═══════════════════════════════════════════════════════════ 14 · FOOTER ══ */

.site-foot {
  position: relative;
  margin-top: var(--section-y);
  padding-block: clamp(3rem, 6vw, 4.5rem) 2rem;
  box-shadow: 0 -1px 0 var(--c-line);
}

.foot-grid {
  display: grid;
  grid-template-columns: 1.6fr 1fr 1fr 1.2fr;
  gap: clamp(1.75rem, 4vw, 3rem);
}
@media (max-width: 900px) { .foot-grid { grid-template-columns: 1fr 1fr; } }
@media (max-width: 560px) { .foot-grid { grid-template-columns: 1fr; } }

.site-foot h3 {
  margin-bottom: .9rem;
  font-size: var(--fs-label);
  font-weight: 600;
  letter-spacing: var(--ls-label);
  text-transform: uppercase;
  color: var(--c-mute);
  font-family: var(--font-body);
}
.foot-blurb { margin-top: 1.1rem; max-width: 34ch; font-size: var(--fs-small); color: var(--c-mute); }
.foot-links { display: flex; flex-direction: column; gap: .55rem; }
.foot-links a { color: var(--c-ink-soft); font-size: var(--fs-small); }
.foot-links a:hover { color: var(--c-accent); }

.social { display: flex; gap: .55rem; margin-top: 1.25rem; }
.social a {
  display: grid; place-items: center;
  width: 38px; height: 38px;
  border-radius: var(--r-pill);
  border: 1px solid var(--c-line);
  background: var(--glass-fill);
  color: var(--c-ink-soft);
}
.social a:hover { color: var(--c-accent); background: var(--glass-fill-2); }
.social svg { width: 16px; height: 16px; }

.foot-base {
  display: flex;
  flex-wrap: wrap;
  gap: .5rem 1.5rem;
  justify-content: space-between;
  margin-top: clamp(2rem, 4vw, 3rem);
  padding-top: 1.5rem;
  box-shadow: 0 -1px 0 var(--c-line);
  font-size: var(--fs-fine);
  color: var(--c-mute);
}

/* Sticky mobile action bar. Phone taps are most of the conversions on a
   salon site, so the two primary actions never scroll away. */
.callbar { display: none; }
@media (max-width: 720px) {
  .callbar {
    position: fixed;
    bottom: 0; left: 0; right: 0;
    z-index: 90;
    display: grid;
    grid-template-columns: 1fr 1fr;
    gap: 1px;
    background: var(--c-line);
    box-shadow: 0 -1px 0 var(--c-line);
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
    padding-bottom: env(safe-area-inset-bottom);
  }
  .callbar a {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: .5rem;
    padding: .95rem;
    background: var(--c-callbar);
    color: var(--c-ink);
    font-size: var(--fs-small);
    font-weight: 500;
  }
  .callbar a.is-primary { background: var(--c-accent); color: var(--c-on-accent); }
  .callbar svg { width: 16px; height: 16px; }
  .site-foot { padding-bottom: 5.5rem; }
}

/* ═══════════════════════════════════════════════════════════ 15 · MOTION ══

   Panels rise and gain their blur as they enter the viewport. The blur is
   animated on the element's own backdrop-filter, so a panel genuinely comes
   into focus rather than just fading — which is the effect the brief asks
   for, and the reason .reveal is applied to the glass panels themselves.

   .js-ready is set by an inline script in each <head>; without JS nothing is
   ever hidden. .reveal-fallback is the 3-second failsafe.                   */

.reveal { transition: opacity var(--dur) var(--ease-out) var(--d, 0s),
                      transform var(--dur) var(--ease-out) var(--d, 0s); }

.js-ready .reveal:not(.is-in) {
  opacity: 0;
  transform: translateY(18px);
}
.js-ready .glass.reveal:not(.is-in) {
  backdrop-filter: blur(0) saturate(1);
  -webkit-backdrop-filter: blur(0) saturate(1);
}
.js-ready.reveal-fallback .reveal {
  opacity: 1 !important;
  transform: none !important;
  backdrop-filter: var(--glass-blur);
  -webkit-backdrop-filter: var(--glass-blur);
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }

  /* The aurora stops moving, the hero photos stop bobbing, and nothing
     translates. The mesh itself stays — it is the design, and a static
     gradient does not trigger vestibular symptoms. */
  .orb, .hero__float { animation: none !important; }

  .reveal, .js-ready .reveal:not(.is-in) {
    opacity: 1 !important;
    transform: none !important;
    transition: none !important;
    backdrop-filter: var(--glass-blur);
    -webkit-backdrop-filter: var(--glass-blur);
  }

  /* Hover lifts only. NOT .orbit__node or .orbit__ring — their transform is
     what puts the six labels on the circle in the first place, so resetting
     it stacks all six behind the disc and the component disappears. Reduced
     motion means "do not animate", not "do not lay out"; the transition
     durations are already collapsed to nothing by the blanket rule below. */
  .frame:hover, .btn:hover,
  .frame:hover .ph img { transform: none !important; }

  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
}
