/* ==========================================================================
   Component (c-)
   Reusable UI parts shared across pages.
   ========================================================================== */

/* --------------------------------------------------------------------------
   Eyebrow / small english caption — used as an editorial layout device
   -------------------------------------------------------------------------- */
.c-eyebrow {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-family: var(--font-sans);
  font-size: var(--fs-eyebrow);
  letter-spacing: var(--ls-eyebrow);
  text-transform: uppercase;
  color: var(--color-text-muted);
}

.c-eyebrow::before {
  content: '';
  display: inline-block;
  width: 1.5rem;
  height: 1px;
  background: currentColor;
}

/* --------------------------------------------------------------------------
   Buttons / CTA links
   -------------------------------------------------------------------------- */
.c-button {
  display: inline-flex;
  align-items: center;
  gap: var(--space-xs);
  font-family: var(--font-sans);
  font-size: var(--fs-small);
  letter-spacing: 0.08em;
  padding-block: var(--space-sm);
  padding-inline: var(--space-lg);
  min-height: 44px;
  border: 1px solid currentColor;
  color: var(--color-charcoal);
  transition: background-color var(--duration-fast) var(--ease-standard),
    color var(--duration-fast) var(--ease-standard);
}

.c-button:hover,
.c-button:focus-visible {
  background: var(--color-charcoal);
  color: var(--color-offwhite);
}

.c-button--primary {
  background: var(--color-charcoal);
  color: var(--color-offwhite);
  border-color: var(--color-charcoal);
}

.c-button--primary:hover,
.c-button--primary:focus-visible {
  background: var(--color-mocha-deep);
  border-color: var(--color-mocha-deep);
  color: var(--color-offwhite);
}

.c-button__arrow {
  display: inline-block;
  transition: transform var(--duration-fast) var(--ease-standard);
}

.c-button:hover .c-button__arrow,
.c-button:focus-visible .c-button__arrow {
  transform: translateX(4px);
}

/* Text link with underline sweep — quieter than a bordered button */
.c-text-link {
  display: inline-flex;
  align-items: center;
  gap: var(--space-2xs);
  font-size: var(--fs-small);
  letter-spacing: 0.06em;
  padding-block: var(--space-2xs);
  background-image: linear-gradient(currentColor, currentColor);
  background-size: 0% 1px;
  background-position: 0 100%;
  background-repeat: no-repeat;
  transition: background-size var(--duration-base) var(--ease-out);
}

.c-text-link:hover,
.c-text-link:focus-visible {
  background-size: 100% 1px;
}

.c-text-link .c-button__arrow {
  transition: transform var(--duration-fast) var(--ease-standard);
}

.c-text-link:hover .c-button__arrow,
.c-text-link:focus-visible .c-button__arrow {
  transform: translateX(4px);
}

/* --------------------------------------------------------------------------
   Primary Navigation (desktop)
   -------------------------------------------------------------------------- */
.c-nav-desktop {
  display: none;
}

@media (min-width: 1024px) {
  .c-nav-desktop {
    display: flex;
  }
}

.c-nav-desktop__list {
  display: flex;
  align-items: center;
  gap: var(--space-lg);
}

.c-nav-desktop__link {
  position: relative;
  display: inline-flex;
  flex-direction: column;
  align-items: center;
  font-size: var(--fs-small);
  letter-spacing: 0.1em;
  padding-block: var(--space-2xs);
}

.c-nav-desktop__link::after {
  content: '';
  position: absolute;
  bottom: -2px;
  left: 50%;
  width: 0;
  height: 1px;
  background: var(--color-charcoal);
  transition: width var(--duration-base) var(--ease-out), left var(--duration-base) var(--ease-out);
}

.c-nav-desktop__link:hover::after,
.c-nav-desktop__link:focus-visible::after {
  width: 100%;
  left: 0;
}

.c-nav-desktop__link[aria-current='page'] {
  color: var(--color-charcoal);
}

.c-nav-desktop__link[aria-current='page']::after {
  width: 100%;
  left: 0;
  background: var(--color-mocha);
}

.c-nav-desktop__cta {
  margin-inline-start: var(--space-sm);
}

/* --------------------------------------------------------------------------
   Mobile Navigation — built on native <details>/<summary>
   Works with zero JavaScript; JS only enhances (close-on-link, Escape, lock scroll).

   `.c-nav-mobile__panel` lives OUTSIDE .l-header entirely (a sibling of
   <header> in the HTML, not nested inside it) — for two independent
   reasons:

   1. Some browsers give <details> its own containing block once open
      (part of the native open/close transition support), which would
      trap a position:fixed descendant inside the details' box instead
      of the viewport.
   2. .l-header has `backdrop-filter` (below), which ALSO creates a new
      containing block for any position:fixed descendant per spec — a
      position:fixed panel left nested inside the header would anchor to
      the header's own (scrolled/offset) box instead of the true
      viewport, landing short of the real screen edges. This is easy to
      miss testing at a tall viewport (there's enough slack that nothing
      visibly clips) and only becomes obvious on a short one, where
      content near the panel's own end - the CTA - lands past the real
      bottom of the screen, outside of what scrolling can ever reach.

   Because the panel is no longer a DOM sibling of <details>, the
   `[open] ~` sibling combinator that used to reveal it can't reach
   across into a different parent — `body:has(.c-nav-mobile[open])`
   below does the same job (show the panel whenever an open
   `.c-nav-mobile` exists anywhere in the page) without requiring the JS
   this module already layers on top. `--z-header` is set above
   `--z-mobile-nav` (foundation.css) so the header — trigger included —
   stacks above the now-detached panel and stays clickable as the close
   control the whole time the panel is open. */
.c-nav-mobile {
  display: block;
  position: relative;
}

@media (min-width: 1024px) {
  .c-nav-mobile {
    display: none;
  }
}

.c-nav-mobile__trigger {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  list-style: none;
  cursor: pointer;
}

.c-nav-mobile__trigger::-webkit-details-marker {
  display: none;
}

.c-nav-mobile__trigger-icon {
  position: relative;
  width: 22px;
  height: 15px;
}

.c-nav-mobile__trigger-icon span {
  position: absolute;
  left: 0;
  width: 100%;
  height: 1px;
  background: var(--color-charcoal);
  transition: transform var(--duration-base) var(--ease-standard),
    opacity var(--duration-fast) var(--ease-standard),
    top var(--duration-base) var(--ease-standard);
}

.c-nav-mobile__trigger-icon span:nth-child(1) { top: 0; }
.c-nav-mobile__trigger-icon span:nth-child(2) { top: 50%; }
.c-nav-mobile__trigger-icon span:nth-child(3) { top: 100%; }

.c-nav-mobile[open] .c-nav-mobile__trigger-icon span:nth-child(1) {
  top: 50%;
  transform: rotate(45deg);
}

.c-nav-mobile[open] .c-nav-mobile__trigger-icon span:nth-child(2) {
  opacity: 0;
}

.c-nav-mobile[open] .c-nav-mobile__trigger-icon span:nth-child(3) {
  top: 50%;
  transform: rotate(-45deg);
}

.c-nav-mobile__panel {
  display: none;
  position: fixed;
  /* `--mobile-nav-offset` is set by mobile-nav.js to the header's current
     on-screen bottom edge (see there for why), so the panel's own top
     starts right below it instead of running underneath it. `--z-header`
     is still kept above `--z-mobile-nav` (see foundation.css) purely so
     the header's own trigger remains the clickable close control — but
     without this offset, that same higher stacking would paint the
     header over this panel's own first item. Defaults to 0 (the old
     full-viewport `inset: 0`) when JS never sets it — the zero-JS
     `:has()` reveal below still works, just without this offset. */
  top: var(--mobile-nav-offset, 0px);
  right: 0;
  bottom: 0;
  left: 0;
  z-index: var(--z-mobile-nav);
  background: var(--color-ivory);
  flex-direction: column;
  width: 100%;
  height: calc(100vh - var(--mobile-nav-offset, 0px));
  height: calc(100dvh - var(--mobile-nav-offset, 0px));
  padding: var(--space-2xl) var(--container-padding);
  overflow-y: auto;
}

/* Vertical centering that degrades to top-aligned + scrollable instead
   of fighting `overflow-y: auto` above. `justify-content: center` used
   to do this job, but centering a flex line that's TALLER than its
   container makes part of the overflow unreachable by scroll in most
   browsers — the classic "justify-content: center clips the start of
   the overflow" flexbox gotcha. Auto margins on the first/last child
   don't have that problem: with room to spare they consume the extra
   space equally (identical visual result to justify-content: center),
   and once content is taller than the panel they simply collapse to 0,
   leaving completely normal top-to-bottom overflow that
   `overflow-y: auto` scrolls through in full — including the CTA at the
   very end. */
.c-nav-mobile__panel > nav {
  margin-block-start: auto;
}

/* Pure CSS — no JavaScript required to reveal the panel when the
   disclosure opens. `:has()` stands in for the `[open] ~` sibling
   combinator this used before the panel moved outside .l-header (see
   the comment above .c-nav-mobile) — same zero-JS behaviour, just
   reaching across a parent boundary instead of a sibling one. */
body:has(.c-nav-mobile[open]) .c-nav-mobile__panel {
  display: flex;
}

@media (min-width: 1024px) {
  /* Guards the edge case where `open` lingers on resize (no-JS, or JS
     hasn't run yet) — the fullscreen panel must never show at desktop
     widths, where the horizontal nav is used instead. */
  body:has(.c-nav-mobile[open]) .c-nav-mobile__panel {
    display: none;
  }
}

.c-nav-mobile__list {
  display: flex;
  flex-direction: column;
  gap: var(--space-md);
}

.c-nav-mobile__link {
  font-family: var(--font-serif);
  font-size: clamp(1.8rem, 1.5rem + 2vw, 2.6rem);
  letter-spacing: 0.03em;
}

.c-nav-mobile__link[aria-current='page'] {
  color: var(--color-mocha);
}

.c-nav-mobile__meta {
  /* End value is the other half of the graceful-centering pair above
     (paired with `.c-nav-mobile__panel > nav`'s margin-block-start:
     auto) — collapses to 0 once content overflows the panel. */
  margin-block: var(--space-2xl) auto;
  padding-block-start: var(--space-lg);
  border-block-start: 1px solid rgba(46, 43, 41, 0.12);
  font-size: var(--fs-small);
  color: var(--color-text-muted);
  display: flex;
  flex-direction: column;
  gap: var(--space-2xs);
}

/* Short-viewport allowance: on a genuinely low viewport height (a small
   phone, or a phone rotated to landscape), the panel's generous padding
   and gaps can add up to more scrolling than is comfortable before
   reaching the CTA. These three rules only trim the mobile nav panel's
   OWN spacing — never the --space-* tokens themselves, which stay
   untouched everywhere else on the site (DESIGN/MENU/ACCESS's own
   typography and whitespace are unaffected). Link/heading font sizes
   are untouched too; only surrounding space is trimmed. Above this
   height, nothing here applies and the panel looks exactly as before. */
@media (max-height: 700px) {
  .c-nav-mobile__panel {
    padding-block: var(--space-lg);
  }

  .c-nav-mobile__list {
    gap: var(--space-sm);
  }

  .c-nav-mobile__meta {
    margin-block: var(--space-lg) auto;
  }
}

/* JS enhancement hook — only active once JS has confirmed control.
   Native <details> flips `display` synchronously on open, so a transition
   declared only on the base/open pair never gets a "from" frame to
   animate. mobile-nav.js instead adds `.is-visible` one frame after open,
   which gives the browser a display:block starting point to transition
   from. Closing still happens instantly (native), which is an accepted
   trade-off for a disclosure widget rather than added JS complexity. */
.has-js .c-nav-mobile__panel {
  opacity: 0;
  transform: translateY(8px);
  transition: opacity var(--duration-base) var(--ease-standard),
    transform var(--duration-base) var(--ease-standard);
}

.has-js .c-nav-mobile__panel.is-visible {
  opacity: 1;
  transform: translateY(0);
}

/* --------------------------------------------------------------------------
   Loading overlay
   Decorative brand intro. Hidden by default; JS reveals it. Never blocks
   content — a safety timeout in loading.js guarantees removal.
   -------------------------------------------------------------------------- */
.c-loading {
  position: fixed;
  inset: 0;
  z-index: var(--z-loading);
  display: none;
  align-items: center;
  justify-content: center;
  background: var(--color-ivory);
}

.c-loading.is-active {
  display: flex;
}

.c-loading__mark {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--space-sm);
}

.c-loading__line {
  width: 1px;
  height: 40px;
  background: var(--color-charcoal);
  transform: scaleY(0);
  transform-origin: top;
}

.c-loading__word {
  display: flex;
  font-family: var(--font-serif);
  font-size: clamp(2.2rem, 1.8rem + 2vw, 3.6rem);
  letter-spacing: 0.3em;
}

.c-loading__letter {
  opacity: 0;
  transform: translateY(6px);
}

.c-loading__sub {
  font-size: var(--fs-caption);
  letter-spacing: 0.32em;
  color: var(--color-text-muted);
  opacity: 0;
}

.c-loading.is-active .c-loading__line {
  animation: loadingLine 600ms var(--ease-out) forwards;
}

.c-loading.is-active .c-loading__letter {
  animation: loadingLetter 560ms var(--ease-out) forwards;
}

.c-loading.is-active .c-loading__letter:nth-child(1) { animation-delay: 520ms; }
.c-loading.is-active .c-loading__letter:nth-child(2) { animation-delay: 600ms; }
.c-loading.is-active .c-loading__letter:nth-child(3) { animation-delay: 680ms; }
.c-loading.is-active .c-loading__letter:nth-child(4) { animation-delay: 760ms; }
.c-loading.is-active .c-loading__letter:nth-child(5) { animation-delay: 840ms; }

.c-loading.is-active .c-loading__sub {
  animation: loadingSub 500ms var(--ease-out) forwards;
  animation-delay: 1200ms;
}

.c-loading.is-exiting {
  transition: opacity var(--duration-slow) var(--ease-standard);
  opacity: 0;
}

@keyframes loadingLine {
  to { transform: scaleY(1); }
}

@keyframes loadingLetter {
  to { opacity: 1; transform: translateY(0); }
}

@keyframes loadingSub {
  to { opacity: 1; }
}

/* --------------------------------------------------------------------------
   TOP Intro
   Full-screen brand entrance for the homepage FV only (see .js-intro —
   the other pages use .c-loading above instead). Hidden by default;
   intro.js is the only thing that ever reveals it, so a JS failure before
   that point simply leaves the FV visible immediately. Decorative
   throughout: aria-hidden/inert are static in the HTML, and intro.js
   removes the element from the DOM once finished, so it can never linger
   and block focus or scrolling.
   -------------------------------------------------------------------------- */
.c-intro {
  display: none;
  position: fixed;
  inset: 0;
  z-index: var(--z-loading);
}

.c-intro.is-active {
  display: block;
}

/* Both panels share one colour (--color-veil) — a soft step deeper than
   the FV's own ivory, not a distinct hue. At rest the two are barely
   distinguishable from the page behind them; the split only reads once
   they move, as a thin veil quietly parting rather than a curtain with a
   drawn boundary. */
.c-intro__panel {
  position: absolute;
  inset-inline: 0;
  height: 50%;
  background: var(--color-veil);
}

.c-intro__panel--top {
  top: 0;
}

.c-intro__panel--bottom {
  bottom: 0;
}

/* LINOA is the visual anchor, not the split line — the group is centred
   as a block via flex, then nudged down slightly. Without the nudge, the
   line + caption trailing below the word pull the group's centre below
   the word itself, leaving LINOA sitting above true screen-centre. The
   nudge is an optical correction (not derived from exact box metrics,
   which shift with the word's own fluid font-size) — adjust the value
   here if it needs revisiting after a real-browser check. */
.c-intro__content {
  position: absolute;
  inset: 0;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: var(--space-xs);
  transform: translateY(18px);
}

.c-intro__word {
  font-family: var(--font-serif);
  font-size: clamp(2.2rem, 1.8rem + 2.5vw, 3.6rem);
  line-height: 1;
  letter-spacing: 0.34em;
  white-space: nowrap;
  /* Optical re-centring against the letter-spacing pulling the word right. */
  padding-inline-start: 0.34em;
  opacity: 0;
}

/* Decorative only, part of the logo unit itself (not a stand-in for the
   panel split, which stays independent — see .c-intro__panel above).
   Sized well short of the wordmark, roughly a third of it, so it reads
   as a small flourish beneath LINOA rather than a rule spanning it. */
.c-intro__line {
  width: clamp(3.5rem, 3rem + 4vw, 6rem);
  height: 1px;
  background: var(--color-charcoal);
  transform: scaleX(0);
  transform-origin: left center;
}

.c-intro__sub {
  font-size: var(--fs-caption);
  line-height: 1.2;
  letter-spacing: 0.3em;
  white-space: nowrap;
  color: var(--color-text-muted);
  opacity: 0;
}

/* Timeline from .is-active (ms):
     80    word fades in                     (dur 480)
     480   line draws left -> right           (dur 550)
     500   sub-text fades in                  (dur 450)
     1330  whole logo unit fades out          (dur 320)
     1430  panels part from the centre split  (dur 780) -> ~2210ms total
           (colour/timing unchanged; logo unit above is independent
           of the split — it no longer marks the boundary) */
.c-intro.is-active .c-intro__word {
  animation: introWordIn 480ms var(--ease-out) 80ms forwards;
}

.c-intro.is-active .c-intro__line {
  animation: introLineDraw 550ms var(--ease-out) 480ms forwards;
}

.c-intro.is-active .c-intro__sub {
  animation: introWordIn 450ms var(--ease-out) 500ms forwards;
}

.c-intro.is-active .c-intro__content {
  animation: introContentOut 320ms var(--ease-standard) 1330ms forwards;
}

.c-intro.is-active .c-intro__panel--top {
  animation: introPanelUp 780ms var(--ease-standard) 1430ms forwards;
}

.c-intro.is-active .c-intro__panel--bottom {
  animation: introPanelDown 780ms var(--ease-standard) 1430ms forwards;
}

@keyframes introWordIn {
  from { opacity: 0; transform: translateY(6px); }
  to { opacity: 1; transform: translateY(0); }
}

@keyframes introLineDraw {
  to { transform: scaleX(1); }
}

@keyframes introContentOut {
  to { opacity: 0; }
}

@keyframes introPanelUp {
  to { transform: translateY(-100%); }
}

@keyframes introPanelDown {
  to { transform: translateY(100%); }
}

/* --------------------------------------------------------------------------
   FV reveal (hero-reveal.js) — initial state is applied by JS only (never
   in static CSS), so content is fully visible by default when JavaScript
   is unavailable. Staggered after the intro via a CustomEvent, not
   IntersectionObserver — see hero-reveal.js.
   -------------------------------------------------------------------------- */
.js-fv-reveal.is-pre-reveal {
  opacity: 0;
  transform: translateY(20px);
}

.js-fv-reveal.is-revealed {
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--duration-slow) var(--ease-out),
    transform var(--duration-slow) var(--ease-out);
}

/* Opacity-only variant: the FV's secondary image already owns `transform`
   via .js-parallax on the same element, so its own entrance must not also
   set transform there (see hero-reveal.js / the HTML for detail). */
.js-fv-reveal-fade.is-pre-reveal {
  opacity: 0;
}

.js-fv-reveal-fade.is-revealed {
  opacity: 1;
  transition: opacity var(--duration-slow) var(--ease-out);
}

/* FV main image only: a slow scale-settle layered on the <img> itself —
   a different element from the .js-fv-reveal fade above (the <figure>),
   so the two transforms never compete for the same box. */
.p-hero__media-main .js-fv-image-scale {
  transform: scale(1.03);
  transition: transform 900ms var(--ease-out);
}

.p-hero__media-main.is-revealed .js-fv-image-scale {
  transform: scale(1);
}

/* --------------------------------------------------------------------------
   Stagger reveal motion (stagger-reveal.js)
   Group-triggered Soft Fade Up / Gallery Reveal / Grid Draw / Menu Reveal /
   Veil Reveal, used by the TOP page's CONCEPT teaser, SELECTED DESIGNS,
   WHY LINOA, NAIL ARTIST and MENU sections, and by the CONCEPT page's
   PAGE HERO, PHILOSOPHY, MESSAGE, WHAT WE VALUE and NAIL ARTIST sections.
   Shares the same safe pattern as the rules above: initial state only
   ever applied by JS, so content is fully visible without it.
   -------------------------------------------------------------------------- */

/* Soft Fade Up — a quieter version of .js-fv-reveal (smaller rise, used
   for staggered text groups rather than single elements). Kept as its
   own rule rather than reusing .js-fv-reveal because the distance
   genuinely differs by design (8-16px here vs .js-fv-reveal's 20px).
   `.js-item-reveal` shares this exact visual treatment but is a distinct
   class: it's observed per-element by reveal.js (MENU's four pricing
   cards and its "ご利用にあたって" note), not group-staggered by
   stagger-reveal.js like `.js-soft-reveal` is everywhere else. A single
   fixed top-anchored trigger doesn't fit a stack of substantial,
   full-width cards the way it does a compact title or grid — the same
   reasoning that put the DESIGN gallery on its own per-element class
   (`.js-image-reveal`) rather than the group mechanism. Keeping the two
   class names separate (rather than teaching reveal.js to also watch
   `.js-soft-reveal`) avoids the same element ever being watched by two
   different Observers at once. */
.js-soft-reveal.is-pre-reveal,
.js-item-reveal.is-pre-reveal {
  opacity: 0;
  transform: translateY(12px);
}

.js-soft-reveal.is-revealed,
.js-item-reveal.is-revealed {
  opacity: 1;
  transform: translateY(0);
  transition: opacity var(--duration-slow) var(--ease-out),
    transform var(--duration-slow) var(--ease-out);
}

/* Gallery Reveal / Soft Image Reveal — a touch more rise than Soft Fade
   Up plus a hint of scale, so each card reads as gently placed into the
   layout rather than just fading in place. Applied to the figure itself
   (image + caption together as one unit); the hover-only scale on a
   gallery <img> (see below) is a different element, so the two never
   compete for the same transform.
   Two class names share this one visual treatment because they're
   triggered by two different mechanisms that must never both watch the
   same element: `.js-gallery-item-reveal` is group-triggered by
   stagger-reveal.js (TOP SELECTED DESIGNS' five photos, CONCEPT NAIL
   ARTIST's small photo — a small, fixed set staggered off one shared
   trigger), while `.js-image-reveal` is observed per-element by
   reveal.js (the DESIGN gallery's many photos — deliberately NOT a
   fixed stagger chain, since with a long list of cards a shared trigger
   would either replay a long artificial delay for whatever's already in
   view, or not help items scrolled to individually). */
.js-gallery-item-reveal.is-pre-reveal,
.js-image-reveal.is-pre-reveal {
  opacity: 0;
  transform: translateY(16px) scale(0.985);
}

.js-gallery-item-reveal.is-revealed,
.js-image-reveal.is-revealed {
  opacity: 1;
  transform: translateY(0) scale(1);
  transition: opacity 850ms var(--ease-out), transform 850ms var(--ease-out);
}

/* Menu Reveal — MENU preview's four items. Fade + rise only (no scale;
   these are text blocks, not photos). Applied to the whole
   .p-menu-preview__item <li>, so its own top border-line (a plain
   border-block-start, not a drawn pseudo-element like WHY LINOA's grid)
   simply fades in as part of the same element — deliberately NOT a
   Grid-Draw-style animated line, to keep this section visually distinct
   from WHY LINOA. */
.js-menu-item-reveal.is-pre-reveal {
  opacity: 0;
  transform: translateY(14px);
}

.js-menu-item-reveal.is-revealed {
  opacity: 1;
  transform: translateY(0);
  transition: opacity 880ms var(--ease-out), transform 880ms var(--ease-out);
}

/* Grid Draw — WHY LINOA's four hairlines (see .p-features__list /
   .p-features__item in project.css for the pseudo-elements themselves)
   drawn in on scroll via scale, instead of being static.
   Gated behind .has-js (added synchronously by main.js, before any
   section-specific module runs) rather than a per-element is-pre-reveal
   class: several of these lines are pseudo-elements of an element that
   ALSO carries a differently-timed line (item 01 carries both the
   vertical divider and the middle horizontal line, on separate ::before/
   ::after boxes), so each needs its own independently-timed "drawn" flag
   rather than sharing one is-revealed toggle. Without JS, .has-js is
   never added, so these rules never match and every line renders at its
   default scale(1) — identical to a plain border. */
.has-js .js-grid-top::before {
  transform: scaleX(0);
}

.has-js .js-grid-top.is-drawn-top::before {
  transform: scaleX(1);
  transition: transform 800ms var(--ease-standard);
}

.has-js .js-grid-middle::after {
  transform: scaleX(0);
}

.has-js .js-grid-middle.is-drawn-middle::after {
  transform: scaleX(1);
  transition: transform 800ms var(--ease-standard);
}

.has-js .js-grid-bottom::after {
  transform: scaleX(0);
}

.has-js .js-grid-bottom.is-drawn-bottom::after {
  transform: scaleX(1);
  transition: transform 800ms var(--ease-standard);
}

@media (min-width: 768px) {
  /* The vertical divider only exists as a pseudo-element from this
     breakpoint up (see project.css) — scoping its gate to the same
     media query keeps SP from ever depending on a line SP doesn't have. */
  .has-js .js-grid-vertical::before {
    transform: scaleY(0);
  }

  .has-js .js-grid-vertical.is-drawn-vertical::before {
    transform: scaleY(1);
    transition: transform 800ms var(--ease-standard);
  }
}

/* Grid Draw — CONCEPT's "WHAT WE VALUE" grid (6 items / 3 rows), the same
   technique as WHY LINOA above but with one extra row, so it needed its
   own class names (js-values-* instead of js-grid-*) rather than forcing
   a 3-row layout through WHY LINOA's fixed 2-row selectors. Timing values
   (800ms line duration, --ease-standard) are unchanged from WHY LINOA. */
.has-js .js-values-top::before {
  transform: scaleX(0);
}

.has-js .js-values-top.is-drawn-top::before {
  transform: scaleX(1);
  transition: transform 800ms var(--ease-standard);
}

.has-js .js-values-row1::after,
.has-js .js-values-row2::after,
.has-js .js-values-row3::after {
  transform: scaleX(0);
}

.has-js .js-values-row1.is-drawn-row1::after {
  transform: scaleX(1);
  transition: transform 800ms var(--ease-standard);
}

.has-js .js-values-row2.is-drawn-row2::after {
  transform: scaleX(1);
  transition: transform 800ms var(--ease-standard);
}

.has-js .js-values-row3.is-drawn-row3::after {
  transform: scaleX(1);
  transition: transform 800ms var(--ease-standard);
}

@media (min-width: 768px) {
  /* Vertical divider only exists as a pseudo-element from this breakpoint
     up (see project.css) — same scoping reason as WHY LINOA's above. */
  .has-js .js-values-vertical::before {
    transform: scaleY(0);
  }

  .has-js .js-values-vertical.is-drawn-vertical::before {
    transform: scaleY(1);
    transition: transform 800ms var(--ease-standard);
  }
}

/* Veil Reveal — a solid veil (same --color-veil as the FV intro's panels)
   sits over the photo and slides clear on its own layer, so it never
   touches the <img>'s or the figure's own transform. The photo itself
   never moves. */
.c-veil {
  position: absolute;
  inset: 0;
  background: var(--color-veil);
  opacity: 0;
  pointer-events: none;
}

.js-veil-image.is-pre-reveal .c-veil {
  opacity: 1;
  transform: translateX(0);
}

.js-veil-image.is-revealed .c-veil {
  /* opacity must stay explicit here (not just inherited from
     .is-pre-reveal above): that class is REMOVED the instant this one is
     added, so without restating opacity:1, it fell straight back to the
     base .c-veil rule's opacity:0 with no transition — the veil vanished
     in the same frame the slide began, making the transform transition
     invisible. Now opacity stays at 1 for the whole slide, and the veil
     only "disappears" because it's clipped by the frame's overflow:hidden
     as it travels past the right edge — a real, visible slide-and-clip
     instead of a flicker. */
  opacity: 1;
  transform: translateX(100%);
  /* Eases in-out rather than the site's usual --ease-out (fast start,
     slow finish): a slightly unhurried lift-off before a natural,
     unhurried exit reads as a veil being drawn back, not a wipe. Scoped
     to this one rule rather than a new shared token, since it's a
     one-off tuned specifically for this motion. */
  transition: transform 900ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* Reverse variant — NAIL ARTIST's veil exits to the LEFT instead of the
   right (CONCEPT's own direction). Everything else — colour, opacity
   handling, duration, easing — is shared from the base rules above via
   the cascade; only the end-state transform needs overriding, so that's
   all this adds. Equal specificity to the base .is-revealed rule, so it
   must stay declared after it to win. */
.js-veil-image--reverse.is-revealed .c-veil {
  transform: translateX(-100%);
}

/* --------------------------------------------------------------------------
   Image frame — consistent aspect-ratio container for all photography
   -------------------------------------------------------------------------- */
.c-image-frame {
  position: relative;
  overflow: hidden;
  background: var(--color-greige);
}

.c-image-frame img {
  width: 100%;
  height: 100%;
  object-fit: cover;
}

.c-reveal-image__frame {
  width: 100%;
  height: 100%;
}

/* Full-width landscape editorial banner — reused wherever a wide real
   photo (MENU, ACCESS) should read as one continuous scene rather than
   be squeezed into a narrower column. */
.c-banner-image {
  aspect-ratio: 16 / 7;
}

@media (max-width: 767px) {
  .c-banner-image {
    aspect-ratio: 4 / 3;
  }
}

/* Parallax layer — separated from reveal/hover transforms per motion
   responsibility rules (own DOM layer avoids transform conflicts). */
.js-parallax {
  will-change: transform;
}

/* --------------------------------------------------------------------------
   Figure caption
   -------------------------------------------------------------------------- */
.c-caption {
  display: flex;
  justify-content: space-between;
  gap: var(--space-sm);
  margin-block-start: var(--space-xs);
  font-size: var(--fs-caption);
  letter-spacing: 0.04em;
  color: var(--color-text-muted);
}

/* --------------------------------------------------------------------------
   Quote block
   -------------------------------------------------------------------------- */
.c-quote {
  font-family: var(--font-serif);
  font-size: clamp(1.6rem, 1.3rem + 1.6vw, 2.6rem);
  line-height: 1.5;
  font-weight: 500;
}

.c-quote--small {
  font-size: var(--fs-h3);
}

.c-quote__cite {
  display: block;
  margin-block-start: var(--space-md);
  font-family: var(--font-sans);
  font-size: var(--fs-small);
  letter-spacing: 0.08em;
  color: var(--color-text-muted);
  font-style: normal;
}

/* --------------------------------------------------------------------------
   FAQ — native <details>/<summary>; open/close works with no JS at all.
   faq-accordion.js optionally layers a height animation on top (see that
   file) but never changes anything in this stylesheet — the accordion's
   motion is driven entirely by the Web Animations API at runtime, so
   there's no is-pre-reveal-style state here to keep in sync with it.
   -------------------------------------------------------------------------- */
.c-faq-item {
  border-block-end: var(--border-hairline);
}

.c-faq-item:first-child {
  border-block-start: var(--border-hairline);
}

.c-faq-item__summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding-block: var(--space-md);
  cursor: pointer;
  font-family: var(--font-serif);
  font-size: var(--fs-h3);
  list-style: none;
}

.c-faq-item__summary::-webkit-details-marker {
  display: none;
}

.c-faq-item__icon {
  flex-shrink: 0;
  position: relative;
  width: 18px;
  height: 18px;
}

.c-faq-item__icon::before,
.c-faq-item__icon::after {
  content: '';
  position: absolute;
  top: 50%;
  left: 50%;
  background: var(--color-charcoal);
  transform: translate(-50%, -50%);
  /* Back to the original --duration-fast (200ms): matching this to the
     panel's own 350ms made the icon feel synced on paper but, on close,
     visibly LATE in practice — because `[open]` (below) only comes off
     once the close animation finishes, so a slower icon transition just
     stacked more delay on top of that. Perceived immediacy mattered more
     than the two numbers matching; see the rule below for the actual
     fix on close. */
  transition: transform var(--duration-fast) var(--ease-standard);
}

.c-faq-item__icon::before {
  width: 18px;
  height: 1px;
}

.c-faq-item__icon::after {
  width: 1px;
  height: 18px;
}

/* The vertical line rotates 90deg onto the same centre point as the
   static horizontal line below it, so it lands exactly on top of it —
   "-" is the two lines overlapping, not one line hidden. No opacity
   change: an earlier version faded this line out/in on top of the
   rotation, which made + -> - read as "the line vanishing" (opacity
   dropping to 0 is instant, since only `transform` is transitioned
   above) while - -> + read as "the line rotating in" — an asymmetric
   pair of motions for what should be one motion played forwards and
   backwards. Transform-only makes both directions the literal reverse
   of each other. */
.c-faq-item[open] .c-faq-item__icon::after {
  transform: translate(-50%, -50%) rotate(90deg);
}

/* faq-accordion.js keeps `[open]` on the <details> for the entire close
   animation (removing it early would let the browser's native
   display:none cut the content off before the height tween finishes) —
   but that means the rule above alone would leave the icon rotated
   until the close animation completes, well after the panel visibly
   started closing. Once the module is actually managing a given item,
   it also toggles `.is-open` on click, synchronously, in both
   directions — so this pair takes over the icon entirely for that item
   (equal specificity to the rule above, declared after it, so it wins
   whenever `.js-faq-icon` is present) and the icon can flip back to "+"
   immediately on close instead of waiting for `[open]` to actually
   change. Items this module doesn't manage (JS disabled, reduced
   motion, no Web Animations API) never get `.js-faq-icon`, so the rule
   above keeps driving their icon exactly as before. */
.c-faq-item.js-faq-icon .c-faq-item__icon::after {
  transform: translate(-50%, -50%);
}

.c-faq-item.js-faq-icon.is-open .c-faq-item__icon::after {
  transform: translate(-50%, -50%) rotate(90deg);
}

.c-faq-item__answer {
  padding-block-end: var(--space-md);
  color: var(--color-text-muted);
  max-width: 62ch;
}

/* --------------------------------------------------------------------------
   Definition list — salon info / spec sheets
   -------------------------------------------------------------------------- */
.c-spec-list {
  display: grid;
  grid-template-columns: auto 1fr;
  gap: var(--space-sm) var(--space-lg);
}

.c-spec-list dt {
  font-size: var(--fs-small);
  letter-spacing: 0.06em;
  color: var(--color-text-muted);
  white-space: nowrap;
}

.c-spec-list dd {
  font-size: var(--fs-body-lg);
}

/* --------------------------------------------------------------------------
   Demo notice badge
   -------------------------------------------------------------------------- */
.c-demo-notice {
  background: var(--color-charcoal);
  color: var(--color-ivory);
}

.c-demo-notice__inner {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: var(--space-2xs) var(--space-sm);
  padding-block: var(--space-xs);
  font-size: var(--fs-caption);
  letter-spacing: 0.03em;
}

.c-demo-notice strong {
  font-weight: 600;
}

/* --------------------------------------------------------------------------
   Numbered index — editorial device for menu / flow steps
   -------------------------------------------------------------------------- */
.c-index-number {
  font-family: var(--font-serif);
  font-size: var(--fs-h2);
  color: var(--color-warm-beige);
  line-height: 1;
}
