/* ============================================================
   services.css — /services/ 空間設計與裝修
   Ported from the "Homepage v5 Mobile" design prototype. Values
   (colors, sizes, easings, breakpoints) are unchanged from the
   prototype; only the scoping changed — what were bare element
   rules there are scoped under `.sv` here so the shared header
   and footer keep their own styles.

   Static styles live inline in services/index.html exactly as the
   prototype authored them. This file holds what inline styles
   cannot express: keyframes, media queries, ::selection, and the
   image placeholder. State-driven styles come from
   assets/js/services.js.

   (Replaces the previous PDF-sheet stylesheet for this page —
   see git history for that design.)
   ============================================================ */

html {
  scroll-behavior: smooth;
}

body {
  background: var(--sv-surface);
  -webkit-font-smoothing: antialiased;
}

/* ── Page root (main.sv) ── the prototype's `body` rule, scoped ── */
.sv {
  /* ── Palette ──
     The prototype shipped its own hex values. These map each one onto
     the site token that plays the same role, so this page moves with
     the rest of the site.

     Contrast on white, measured:
       #a5793f (prototype gold)      3.9:1  ✗ below AA for body text
       --color-accent-deep #7c6a4e   5.2:1  ✓  ← gold text
       --color-accent     #c9b47a    2.0:1     decorative / on dark only
       --color-text-muted #6b6660    5.7:1  ✓  ← secondary copy
       --color-text-secondary #6f6f6f 4.9:1 ✓  ← faint labels
     The prototype's ink greys ran rgba(26,26,26,.35–.55), i.e. 2.5–3.5:1,
     all below AA. They collapse onto the two passing tokens; the visual
     hierarchy is carried by size and tracking, which are unchanged. */
  --sv-accent: var(--color-accent-deep);
  --sv-accent-rgb: 124, 106, 78;
  --sv-accent-on-dark: var(--color-accent);

  --sv-ink: var(--color-text-primary);
  --sv-ink-rgb: 26, 26, 26;
  --sv-body: var(--color-text-muted);
  --sv-meta: var(--color-text-secondary);

  --sv-surface: var(--color-background-primary);
  --sv-paper: var(--color-background-secondary);
  --sv-chassis: var(--color-surface-dark);

  /* Trade schedule phases — three steps of a neutral grey ramp.
     The prototype's #c9c3ba first phase was 1.7:1 on white, near
     invisible; #aaa is 2.3:1 and still reads as the lightest of the
     three. Neutral rather than warm on purpose: a bar turns
     --sv-accent when its row is open, and warm-on-warm made the
     selection hard to pick out. A text legend labels them either way. */
  --sv-phase-early: var(--color-border-primary);
  --sv-phase-mid: var(--color-text-secondary);
  --sv-phase-late: var(--color-text-primary);

  color: var(--sv-ink);
  font-family: "Noto Sans TC", sans-serif;
  font-weight: 300;
}

.sv a {
  color: var(--sv-ink);
  text-decoration: none;
}

.sv a:hover {
  color: var(--sv-accent);
}

.sv ::selection {
  background: var(--sv-accent);
  color: var(--sv-surface);
}

/* ── Keyframes ── */
@keyframes drawLine {
  from {
    stroke-dashoffset: 320;
  }

  to {
    stroke-dashoffset: 0;
  }
}

@keyframes marquee {
  from {
    transform: translateX(0);
  }

  to {
    transform: translateX(-50%);
  }
}

@keyframes chipIn {
  from {
    opacity: 0;
    transform: translateY(14px);
  }

  to {
    opacity: 1;
    transform: none;
  }
}

@keyframes tdot {

  0%,
  80%,
  100% {
    opacity: 0.25;
  }

  40% {
    opacity: 1;
  }
}

/* Animates the independent `scale` property, not `transform`. The open
   row's + is rotated 45° into an × by an inline transform, and an
   animation on `transform` overrides inline styles outright — it would
   un-rotate that row back to a + for the length of the pulse. `scale`
   composes with `transform` instead of replacing it, so the × stays an
   × while it pulses. */
@keyframes svPlusHint {

  0%,
  100% {
    scale: 1;
  }

  30% {
    scale: 1.5;
  }

  65% {
    scale: 1;
  }
}

/* ── Image placeholder ──
   Stands in for the prototype's <image-slot>. Drop an <img> inside
   and it covers the label; the label is the art-direction note. */
.sv-slot {
  position: relative;
  width: 100%;
  height: 100%;
  display: flex;
  align-items: center;
  justify-content: center;
  text-align: center;
  padding: 16px;
  box-sizing: border-box;
  background: var(--sv-paper);
  color: var(--sv-meta);
  font-size: 12px;
  letter-spacing: 0.14em;
  line-height: 1.9;
}

.sv-slot img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  padding-bottom: 8px;
}

/* You were right to check — .sv-slot has three jobs on this page, and
   they don't want the same fit:

     [data-panel]  space-type photos      → must fill, edge to edge
     [data-laptop] proposal PDF pages     → must NOT crop; a floor plan
                                            loses information at the edges
     works grid    text-only placeholders → no <img> at all

   So `cover` is scoped to the panels rather than changed on the base
   rule. padding-bottom goes too: on an inset:0 absolute image it isn't
   spacing, it shrinks the content box and leaves an 8px strip of
   --sv-paper along the bottom — the second white bar, and the one that
   survives switching to cover on its own. */
.sv [data-panel] .sv-slot img {
  object-fit: cover;
  padding-bottom: 0;
}

/* ── Interactive affordances ──
   The prototype drove these from onClick handlers on plain divs.
   Keyboard users get the same targets; no visual change. */
.sv [role="button"] {
  cursor: pointer;
}

/* ── Schedule rows: expandability hint ──
   The + is a row's only affordance, and on touch there is no hover
   to discover it with. services.js adds this class once, staggered,
   when the table scrolls into view: the pulse sweeps down the column
   and reads as "all of these open" without putting a label into a
   deliberately sparse table. It is removed on animationend so the
   inline transform that rotates + into × is never held hostage.
   The tinted chip behind the + (services.js) is the persistent half
   of this — it still says "button" to anyone who scrolls past
   before the sweep plays, or who has reduced motion on. */
.sv-plus-hint {
  animation: svPlusHint 0.9s cubic-bezier(.22, 1, .22, 1);
}

/* ── Hover states ──
   The prototype expressed these with a `style-hover` attribute its
   runtime applied by swapping inline styles, so they always beat
   the base inline style. Where the base value is still inline (or
   written by services.js) the rule needs !important to win the
   same way; where the base moved into this file, it doesn't. */
.sv-link-arrow:hover {
  gap: 18px !important;
  color: var(--sv-ink) !important;
}

.sv-link-gold:hover {
  color: var(--sv-ink) !important;
}

.sv-tab:hover {
  color: var(--sv-accent) !important;
}

.sv-dot:hover {
  border-color: var(--sv-accent) !important;
}

.sv-trade-row:hover {
  background: rgba(var(--sv-accent-rgb), 0.05) !important;
}

.sv-faq-reset:hover {
  color: var(--sv-accent) !important;
}

.sv-faq-chip:hover {
  background: var(--sv-accent) !important;
  color: var(--sv-surface) !important;
  border-color: var(--sv-accent) !important;
}

.sv-cta-line:hover {
  background: var(--sv-surface) !important;
  color: var(--sv-ink) !important;
}

.sv-cta-tel:hover {
  border-color: var(--sv-accent-on-dark) !important;
  color: var(--sv-accent-on-dark) !important;
}

.sv-work-img:hover {
  transform: scale(1.05);
}

/* ── Sticky sections vs. the fixed header ──
   The site header is `position: fixed` and reappears on any upward
   scroll, so a sticky pane pinned at top:0 sits underneath it. The
   prototype's 56px (mobile) / 86px (desktop) top padding was set
   against no header at all, and 56px is less than the 75px mobile
   header — the "HOW WE WORK" eyebrow disappeared behind it.

   --header-clearance (set by header.html's scroll handler) tracks
   whether the header is actually showing right now — 0px while it's
   hidden, var(--header-height) while visible. This section is a
   position:sticky, height:100svh pane, so permanently reserving the
   full header height cropped content for the entire time the header
   is scrolled away (i.e. most of a downward scroll through here).
   The var()'s second argument is the pre-JS fallback.

   No max(56px, ...) floor here on purpose — that was fine for a
   static value but would clamp the collapsed (header-hidden) state
   right back up to 56px, defeating the point. The +12px is the only
   floor now: breathing room above the content when the header's gone. */
.sv [data-proc-sticky] {
  padding: calc(var(--header-clearance, var(--header-height)) + 12px) 20px 30px;
  transition: padding-top .35s ease;
}

/* ── Process stage views ──
   The prototype emitted one <sc-for> row and repeated it; the port
   spells out all four flows × five stages so every step is in the
   markup. These carry the prototype's per-stage inline values so
   they aren't written out twenty times. Position/opacity/transform
   are applied per stage by services.js. */
.sv-stage {
  position: absolute;
  inset: 0;
}

/* align-content matters as much as align-items here. On mobile this is
   a single column, so the number block and the body are two auto rows
   inside a height:100% grid — and align-content's initial `normal`
   resolves to stretch, which hands the leftover viewport height to the
   rows and drops the body into the middle of the pane. `align-items:
   start` doesn't help: it aligns each item inside its own row, not the
   rows themselves. Starting the rows packs them under the number, with
   `gap` as the only spacing. Above 900px the grid is two columns and
   therefore one row, so this changes nothing there. */
.sv-stage-grid {
  display: grid;
  grid-template-columns: 1fr;
  gap: 20px;
  align-items: start;
  align-content: start;
  height: 100%;
}

.sv-stage-num-wrap {
  display: flex;
  align-items: baseline;
  gap: 14px;
}

.sv-stage-num {
  margin: 0;
  font-family: "Noto Serif TC", serif;
  font-weight: 400;
  font-size: clamp(44px, 7.5vw, 110px);
  line-height: 1;
  color: var(--sv-accent);
}

.sv-stage-en {
  margin: 10px 0 0;
  font-size: 10.5px;
  letter-spacing: 0.3em;
  color: var(--sv-meta);
}

.sv-stage-body {
  max-width: 640px;
  min-width: 0;
}

.sv-stage-title {
  margin: 0 0 12px;
  font-family: "Noto Serif TC", serif;
  font-weight: 600;
  font-size: clamp(23px, 5.6vw, 34px);
  letter-spacing: 0.1em;
}

.sv-stage-desc {
  margin: 0 0 18px;
  font-size: clamp(13px, 3.6vw, 14.5px);
  line-height: 1.9;
  letter-spacing: 0.05em;
  color: var(--sv-body);
}

.sv-chips {
  display: flex;
  flex-wrap: wrap;
  gap: 8px;
}

.sv-chip {
  padding: 7px 13px;
  border: 1px solid rgba(var(--sv-ink-rgb), 0.16);
  font-size: 12.5px;
  letter-spacing: 0.1em;
  color: var(--sv-body);
}

.sv-pay {
  display: inline-flex;
  align-items: center;
  gap: 12px;
  margin-top: 16px;
  padding: 9px 14px;
  background: rgba(var(--sv-accent-rgb), 0.07);
  border-left: 2px solid var(--sv-accent);
}

.sv-pay-label {
  font-size: 10.5px;
  letter-spacing: 0.28em;
  color: var(--sv-accent);
  white-space: nowrap;
}

.sv-pay-text {
  font-size: 13px;
  letter-spacing: 0.08em;
}

/* ── Chrome bar ── the "window" title strip on the quote sheet,
      the trade schedule and the contact laptop. */
.sv-chrome {
  display: flex;
  align-items: center;
  gap: 7px;
  padding: 11px 16px;
  background: var(--sv-paper);
  border-bottom: 1px solid rgba(var(--sv-ink-rgb), 0.08);
}

.sv-chrome-dot {
  width: 10px;
  height: 10px;
  border-radius: 50%;
  flex: none;
}

.sv-chrome-name {
  margin-left: 10px;
  font-family: ui-monospace, Menlo, monospace;
  font-size: 11px;
  letter-spacing: 0.08em;
  color: var(--sv-body);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* ── Why-us cards ── four cards, identical but for their copy ── */
.sv-why-card {
  border-top: 2px solid rgba(var(--sv-ink-rgb), 0.14);
  padding-top: 24px;
  transition: all 0.4s;
}

.sv-why-card:hover {
  border-top-color: var(--sv-accent);
  transform: translateY(-6px);
}

.sv-why-en {
  margin: 0 0 12px;
  font-size: 11px;
  letter-spacing: 0.3em;
  color: var(--sv-accent);
}

.sv-why-zh {
  margin: 0 0 12px;
  font-family: "Noto Serif TC", serif;
  font-weight: 600;
  font-size: clamp(19px, 4.8vw, 21px);
  letter-spacing: 0.1em;
}

.sv-why-desc {
  margin: 0;
  font-size: 13.5px;
  line-height: 2;
  letter-spacing: 0.04em;
  color: var(--sv-body);
}

/* ── Stats ── four count-up figures ── */
.sv-stat {
  background: var(--sv-surface);
  padding: clamp(22px, 3vw, 40px) clamp(18px, 2.5vw, 36px);
}

.sv-stat-num {
  margin: 0;
  font-family: "Noto Serif TC", serif;
  font-weight: 400;
  font-size: clamp(36px, 8vw, 58px);
  line-height: 1;
  color: var(--sv-accent);
}

.sv-stat-unit {
  font-size: 0.45em;
}

.sv-stat-label {
  margin: 12px 0 0;
  font-size: 12.5px;
  letter-spacing: 0.14em;
  color: var(--sv-body);
}

/* ── Section eyebrow + heading ── repeated across every section ── */
.sv-eyebrow {
  margin: 0 0 12px;
  font-size: 11.5px;
  letter-spacing: 0.44em;
  color: var(--sv-accent);
}

.sv-h2 {
  margin: 0;
  font-family: "Noto Serif TC", serif;
  font-weight: 500;
  font-size: clamp(28px, 6.6vw, 50px);
  letter-spacing: 0.06em;
}

/* ── Hero captions ── five scroll-stepped panels ── */
.sv-hero-eyebrow {
  margin: 0 0 16px;
  font-size: 11px;
  letter-spacing: 0.4em;
  color: var(--sv-accent);
}

.sv-hero-h2 {
  margin: 0;
  font-family: "Noto Serif TC", serif;
  font-weight: 500;
  font-size: clamp(23px, 6vw, 40px);
  line-height: 1.6;
  letter-spacing: 0.14em;
}

.sv-hero-note {
  margin: 18px 0 0;
  font-size: 13.5px;
  letter-spacing: 0.12em;
  color: var(--sv-body);
}

/* ── Contact device ── laptop on desktop, phone on mobile ──
   Same markup and the same 1.7-ratio proposal pages either way; only
   the chassis changes shape. That works because a tall screen simply
   shows ~2.5 of those landscape pages stacked instead of one — which
   is what a PDF looks like on a phone anyway — so nothing needs
   re-cropping or a second set of assets.

   The laptop's width came from `calc((100svh - 340px) * 1.7)`, a
   ratio-driven figure that on a phone left the device short and wide
   with a large dead band beneath it in the 100svh sticky pane. Here
   the device is a flex child that grows into that height instead, and
   its width is capped so the CTA panel inside doesn't get cramped.

   Portrait only: a landscape phone has no spare height to reclaim, so
   it keeps the laptop, which already fits that shape. */
@media (max-width: 899px) and (orientation: portrait) {

  .sv [data-laptop] {
    width: min(320px, 82vw) !important;
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    margin-bottom: clamp(18px, 4vh, 40px);
  }

  .sv [data-lap-chassis] {
    flex: 1 1 auto;
    min-height: 0;
    display: flex;
    flex-direction: column;
    border-radius: 34px !important;
    padding: 8px !important;
  }

  .sv [data-lap-screen] {
    aspect-ratio: auto !important;
    flex: 1 1 auto;
    min-height: 0;
    border-radius: 27px !important;
  }

  /* Traffic lights are a desktop-window idiom. Dropping them leaves the
     strip reading as an app title bar, which is right on a phone. */
  .sv [data-laptop] .sv-chrome-dot {
    display: none;
  }

  .sv [data-laptop] .sv-chrome {
    justify-content: center;
  }

  /* No hinge or base on a phone. */
  .sv [data-lap-base] {
    display: none !important;
  }
}

/* ── Contact CTA buttons ──
   Left alone these size to their labels, so "LINE 預約諮詢 →" comes out
   wider than the phone number.

   A viewport media query is the wrong tool: these sit inside the device
   mockup, whose width is min(980px, 92vw, calc((100svh - 340px) * 1.7)) —
   so a short desktop window shrinks the box while the viewport stays wide.
   The buttons have to answer to their own container, not the screen.

   flex-basis:0 makes the pair divide the row evenly whatever the labels
   say, so they are equal at every size. Below the width where two can sit
   side by side without the LINE label wrapping mid-phrase, each takes a
   full row instead — still equal, just stacked. Centring matters once the
   box is wider than its text, or the label hugs the left edge.

   Only flex and justify-content are set: neither appears in the inline
   style, so no !important is needed. Without container-query support this
   degrades to the flex-basis:0 case, which is still equal width. */
.sv [data-cta-actions] {
  container-type: inline-size;
}

.sv .sv-cta-line,
.sv .sv-cta-tel {
  flex: 1 1 0;
  justify-content: center;
}

@container (max-width: 420px) {

  .sv .sv-cta-line,
  .sv .sv-cta-tel {
    flex-basis: 100%;
  }
}

/* ── Responsive ── breakpoints and rules verbatim from the prototype ── */
@media (max-height: 960px) {
  .sv [data-quote-scroll] {
    max-height: max(200px, calc(100vh - 560px));
    overflow-y: auto;
  }
}

@media (min-width: 900px) {
  .sv [data-panels] {
    flex-direction: row !important;
    height: min(70vh, 620px) !important;
  }

  .sv [data-sv-grid] {
    grid-template-columns: 300px 1fr !important;
    gap: 64px !important;
  }

  .sv [data-sv-num] {
    display: block !important;
  }

  .sv [data-stats] {
    grid-template-columns: repeat(4, 1fr) !important;
  }

  .sv [data-proc-sticky] {
    padding: calc(var(--header-clearance, var(--header-height)) + 12px) 72px 52px !important;
  }

  .sv [data-proc-hint] {
    display: block !important;
  }

  .sv [data-months] {
    margin-left: 194px !important;
  }

  .sv [data-trade-desc] {
    padding-left: 48px !important;
  }

  .sv [data-q-head] {
    gap: 18px !important;
  }

  .sv [data-q-name] {
    flex: 0 0 118px !important;
  }

  .sv [data-q-desc] {
    display: block !important;
  }

  .sv [data-q-est] {
    flex: 0 0 88px !important;
  }
}

@media (min-width: 700px) {
  .sv [data-why-cards] {
    grid-template-columns: repeat(2, 1fr) !important;
  }
}

@media (min-width: 1100px) {
  .sv [data-why-cards] {
    grid-template-columns: repeat(4, 1fr) !important;
  }
}

@media (min-width: 1000px) {
  .sv [data-faq-grid] {
    grid-template-columns: minmax(0, 1fr) minmax(0, 1.7fr) !important;
    gap: 80px !important;
  }
}

/* ── Reduced motion ──
   The marquee and the drawn underline are decorative and loop on
   their own. The scroll-driven sequences are user-controlled and
   stay as designed. */
@media (prefers-reduced-motion: reduce) {

  .sv [data-works-track],
  .sv [data-underline] {
    animation: none !important;
  }
}
