/* Flooring Masters - New York City. Realisation of the Arcke design source, carried from the
   cluster's approved design baseline. The baseline repository and commit are recorded in
   data/template.json and DESIGN-DNA.md, deliberately NOT here: this file is served to customers,
   and a sibling site's name in served bytes is that site's business truth, not this one's.
   Tokens and rationale in DESIGN-DNA.md. */

:root {
  --ground: #101010;
  --surface: #161616;
  --surface-2: #282828;
  --accent: #d8ab7a;
  --accent-deep: #c79e70;
  --ink: #ffffff;
  --body: #b9b4ae;
  --muted: #9a948d;
  --paper: #f8f8f8;
  --paper-ink: #101010;
  --paper-body: #4a4a4a;
  --measure: 68ch;
  --pad: clamp(1.25rem, 4vw, 2.5rem);
  --band: clamp(3.5rem, 8vw, 7rem);

  /* ONE timing language for the whole site. Three durations and two curves: a frequent control
     is fast enough to feel instant, a surface change is noticeable without being a performance,
     and only a once-per-section reveal is allowed to take real time. Inventing an easing per
     section is the thing that reads as unfinished. */
  --t-fast: 140ms;
  --t-mid: 260ms;
  --t-slow: 680ms;
  --ease-out: cubic-bezier(.22, .61, .36, 1);
  --ease-in: cubic-bezier(.55, .06, .68, .19);

  /* Depth on a near-black ground is a light top edge plus a soft deep shadow. A grey border
     alone leaves every panel on the same plane, which is what made the whole page read flat. */
  --lift: inset 0 1px 0 rgba(255, 255, 255, .045), 0 20px 44px -30px rgba(0, 0, 0, .95);
  --lift-hover: inset 0 1px 0 rgba(216, 171, 122, .3), 0 26px 52px -28px rgba(0, 0, 0, 1);
}

*, *::before, *::after { box-sizing: border-box; }

html { scroll-behavior: smooth; -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--ground);
  color: var(--body);
  font-family: Montserrat, "Helvetica Neue", Arial, sans-serif;
  font-size: 16px;
  line-height: 1.7;
  font-weight: 400;
}

@media (min-width: 768px) { body { font-size: 18px; } }

h1, h2, h3, h4 {
  color: var(--ink);
  font-weight: 600;
  line-height: 1.15;
  margin: 0 0 0.6em;
  letter-spacing: -0.01em;
}

h1 { font-size: clamp(2rem, 6vw, 3.4rem); }
h2 { font-size: clamp(1.55rem, 4vw, 2.4rem); }
h3 { font-size: clamp(1.1rem, 2.4vw, 1.3rem); }

p { margin: 0 0 1.1em; max-width: var(--measure); }
p:last-child { margin-bottom: 0; }

a { color: var(--accent); text-decoration: none; transition: color var(--t-fast) var(--ease-out); }
a:hover { color: var(--ink); }

img { max-width: 100%; height: auto; display: block; }

:focus-visible { outline: 3px solid var(--accent); outline-offset: 3px; }

.wrap { width: 100%; max-width: 1200px; margin: 0 auto; padding-inline: var(--pad); }
.skip {
  position: absolute; left: -9999px; top: 0; background: var(--accent);
  color: #101010; padding: .8rem 1.2rem; z-index: 100; font-weight: 600;
}
.skip:focus { left: 0; }

/* ---------------------------------------------------------------- eyebrow + titles */

.eyebrow {
  display: block; font-size: .72rem; font-weight: 600; letter-spacing: .22em;
  text-transform: uppercase; color: var(--accent); margin: 0 0 1rem;
}

/* The template's signature: a very large ghosted word behind the real heading,
   with a short camel rule under it. Realised without the animated dot. */
.stitle { position: relative; text-align: center; margin: 0 auto var(--pad); }
.stitle[data-ghost]::before {
  content: attr(data-ghost);
  position: absolute; inset: auto 0 auto 0; top: 50%; transform: translateY(-64%);
  font-size: clamp(2.75rem, 10.5vw, 7rem); font-weight: 700; text-transform: uppercase;
  color: rgba(255, 255, 255, .038); line-height: 1; letter-spacing: -.02em;
  pointer-events: none; white-space: nowrap; overflow: hidden;
}
.stitle h2 { position: relative; z-index: 1; margin-bottom: 1.25rem; }
.stitle h2::after {
  content: ""; display: block; width: 75px; height: 2px; background: var(--accent);
  margin: 1.25rem auto 0;
}
.stitle p { margin: 0 auto; color: var(--body); max-width: 62ch; }
.stitle.light h2 { color: var(--paper-ink); }
.stitle.light[data-ghost]::before { color: rgba(16, 16, 16, .045); }
.stitle.light p { color: var(--paper-body); }

/* ---------------------------------------------------------------- buttons */

.btn {
  display: inline-block; font-size: .82rem; font-weight: 600; letter-spacing: .12em;
  text-transform: uppercase; padding: 1rem 1.9rem; border: 1px solid var(--accent);
  transition: background var(--t-mid) var(--ease-out), color var(--t-mid) var(--ease-out),
    border-color var(--t-mid) var(--ease-out), transform var(--t-fast) var(--ease-out);
}
/* A control you press must say so. 1px is enough and costs nothing to read. */
.btn:active { transform: translateY(1px); }
.btn-primary { background: var(--accent); color: #101010; }
.btn-primary:hover { background: var(--ink); border-color: var(--ink); color: #101010; }
.btn-ghost { background: transparent; color: var(--ink); }
.btn-ghost:hover { background: var(--accent); color: #101010; }
.btn-row { display: flex; flex-wrap: wrap; gap: .9rem; margin-top: 1.8rem; }

/* ---------------------------------------------------------------- header */

.site-header {
  position: sticky; top: 0; z-index: 40; background: rgba(16, 16, 16, .96);
  border-bottom: 1px solid var(--surface-2); backdrop-filter: blur(6px);
}
.header-inner { display: flex; align-items: center; gap: 1rem; min-height: 76px; }
.brand { display: flex; align-items: center; gap: .7rem; margin-right: auto; }
.brand img { height: 46px; width: auto; }
.nav-toggle {
  margin-left: auto; background: none; border: 1px solid var(--surface-2); color: var(--ink);
  font: inherit; font-size: .78rem; letter-spacing: .12em; text-transform: uppercase;
  padding: .75rem 1rem; cursor: pointer; min-height: 44px;
}
.site-nav ul { display: flex; gap: 1.6rem; list-style: none; margin: 0; padding: 0; }
.site-nav a {
  color: var(--ink); font-size: .8rem; font-weight: 500; letter-spacing: .08em;
  text-transform: uppercase; display: inline-block; padding: .7rem 0; min-height: 44px;
}
.site-nav a:hover, .site-nav a[aria-current="page"] { color: var(--accent); }
.header-call { display: none; }

/* THE HEADER BREAKPOINT.
   The nav and the phone button both appeared at 980px, and between roughly 980 and 1140 there is
   not room on one line for a 184px logo, six nav items and a phone button — so the number wrapped
   and the header grew a second row. Measured on the real header rather than guessed: the row needs
   about 1140px. Below that the toggle stays, which is a complete header rather than a broken one,
   and the fixed call bar carries the phone at those widths. */
@media (max-width: 1139px) {
  .site-nav { display: none; width: 100%; padding: 0 0 1.2rem; }
  .site-nav.open { display: block; }
  .site-nav ul { flex-direction: column; gap: 0; }
  .site-nav li { border-top: 1px solid var(--surface-2); }
  .site-nav a { display: block; padding: .95rem 0; }
  .header-inner { flex-wrap: wrap; }
}
@media (min-width: 1140px) {
  .nav-toggle { display: none; }
  .header-call { display: inline-block; margin-left: 1.6rem; }
  .site-nav ul { gap: 1.35rem; }
}
/* The header row never wraps at desktop: if anything ever does not fit, it scrolls rather than
   silently becoming two rows. */
@media (min-width: 1140px) { .header-inner { flex-wrap: nowrap; } }

/* ---------------------------------------------------------------- hero */

.hero { position: relative; background: var(--surface); overflow: hidden; }
.hero-media { position: absolute; inset: 0; }
.hero-media img { width: 100%; height: 100%; object-fit: cover; }
/* The scrim exists to hold text contrast, not to hide the photograph. The first version ran to
   94% black across the copy column and rendered the hero 78% near-black: the image was served and
   invisible. This is the lightest pair that still clears 4.5:1 for the lede over the brightest
   part of these photographs, measured rather than guessed. */
.hero-media::after {
  content: ""; position: absolute; inset: 0;
  background:
    linear-gradient(100deg, rgba(16,16,16,.82) 0%, rgba(16,16,16,.62) 42%, rgba(16,16,16,.18) 100%),
    linear-gradient(0deg, rgba(16,16,16,.55) 0%, rgba(16,16,16,.1) 45%, rgba(16,16,16,.35) 100%);
}
@media (max-width: 767px) {
  /* One column on a phone, so the copy sits over the whole frame and needs a flat scrim. */
  .hero-media::after {
    background: linear-gradient(0deg, rgba(16,16,16,.86) 0%, rgba(16,16,16,.7) 55%, rgba(16,16,16,.72) 100%);
  }
}
.hero-inner { position: relative; padding-block: clamp(3.5rem, 10vw, 7.5rem); }
.hero-copy { max-width: 42rem; }
/* Over a photograph the accent camel bottoms out at 4.01:1 on the darkest hero, so the hero
   eyebrow takes a lighter tint of the same colour. Measured across all six heroes: 5.20:1 worst.
   Everywhere else the camel sits on the flat ground and stays as it is. */
.hero .eyebrow { color: #e6c6a4; }
/* The hero lede sits over a photograph, so it does not take the muted body colour the dark
   flat ground allows. Measured over the real hero images with the scrim composited: the
   muted value came out at 2.96:1 and this clears 4.5. */
.hero-copy p.lede { font-size: clamp(1rem, 2.2vw, 1.2rem); color: #ece7e2; }
.hero.plain { background: var(--surface); }

/* ---------------------------------------------------------------- sections */

.band { padding-block: var(--band); }
.band.surface { background: var(--surface); }
.band.paper { background: var(--paper); color: var(--paper-body); }
.band.paper h2, .band.paper h3 { color: var(--paper-ink); }
.band.paper a { color: #8a6437; }
.band.paper a:hover { color: var(--paper-ink); }

/* ---------------------------------------------------------------- daylight strip */

.daylight { border-top: 1px solid var(--surface-2); margin-top: var(--pad); }
.daylight li {
  display: grid; grid-template-columns: auto 1fr; gap: 1rem 1.4rem; align-items: start;
  padding: 1.6rem 0; border-bottom: 1px solid var(--surface-2); list-style: none;
}
.daylight ol { margin: 0; padding: 0; }
.daylight .n {
  font-size: .78rem; font-weight: 700; letter-spacing: .12em; color: var(--accent);
  padding-top: .35rem;
}
.daylight h3 { margin: 0 0 .4rem; font-size: 1.05rem; }
.daylight p { margin: 0; max-width: 62ch; }
@media (min-width: 900px) {
  .daylight .dl-body { display: grid; grid-template-columns: 16rem 1fr; gap: 1.4rem; align-items: start; }
  .daylight h3 { margin: 0; }
}

/* ---------------------------------------------------------------- card grids */

/* align-items: start, deliberately. Three building-type cards carry four, five and twelve
   lines of copy, and stretching them to a common height left two of the three two-thirds empty
   with the text jammed against the top. Natural heights read as editorial; equal heights around
   unequal copy read as a template nobody finished. */
.grid { display: grid; gap: 1.25rem; margin-top: var(--pad); align-items: start; }
@media (min-width: 700px) { .grid.two { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 700px) { .grid.three { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1000px) { .grid.three { grid-template-columns: repeat(3, 1fr); } }

.card {
  background: var(--surface); border: 1px solid var(--surface-2); padding: 1.9rem;
  box-shadow: var(--lift);
  transition: border-color var(--t-mid) var(--ease-out), transform var(--t-mid) var(--ease-out),
    box-shadow var(--t-mid) var(--ease-out);
}
.card.has-media { padding: 0; display: flex; flex-direction: column; }
.card.has-media .card-body { padding: 1.6rem 1.9rem 1.9rem; display: flex; flex-direction: column; flex: 1; }
.card-media { overflow: hidden; background: var(--surface-2); }
.card-media img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block;
  transition: transform .6s var(--ease-out);
}
.card.buildings-card .card-media img { aspect-ratio: 3 / 2; }
@media (hover: hover) and (pointer: fine) {
  .card.has-media:hover .card-media img { transform: scale(1.05); }
  .card:hover { border-color: var(--accent); transform: translateY(-4px); box-shadow: var(--lift-hover); }
}
.card h3 { margin-bottom: .6rem; }
.card p { margin-bottom: 0; }

/* One link per card, and its target is the whole card. The overlay is what makes a 22px line of
   heading text a full-card tap target on a phone. */
.card.linked { position: relative; }
.card.linked h3 a { color: var(--ink); }
.card.linked h3 a::after { content: ""; position: absolute; inset: 0; }
.card.linked:hover h3 a, .card.linked:focus-within h3 a { color: var(--accent); }
.card.linked:focus-within { border-color: var(--accent); outline: 3px solid var(--accent); outline-offset: 3px; }
.card.linked h3 a:focus-visible { outline: none; }
.card .more, .scard .more {
  display: inline-block; margin-top: auto; padding-top: 1.2rem; font-size: .78rem; font-weight: 600;
  letter-spacing: .12em; text-transform: uppercase; color: var(--accent);
}
.band.paper .card.linked h3 a { color: var(--paper-ink); }
.band.paper .card.linked:hover h3 a { color: #8a6437; }
.band.paper .card { background: #fff; border-color: #e2e2e2; }

/* ---------------------------------------------------------------- steps */

/* Bounded AND centred, for the same reason the FAQ is. Under a centred section title a
   left-aligned 68ch column leaves half the band empty beside it, which reads as a layout bug
   rather than a choice — it is the defect the FAQ was already repaired for, in another block. */
.steps { counter-reset: st; list-style: none; margin: var(--pad) auto 0; padding: 0; max-width: 52rem; }
.steps li {
  counter-increment: st; position: relative; padding: 0 0 1.6rem 3.4rem;
  border-left: 1px solid var(--surface-2); margin-left: 1rem;
}
.steps li::before {
  content: counter(st, decimal-leading-zero); position: absolute; left: -1.05rem; top: 0;
  width: 2.1rem; height: 2.1rem; display: grid; place-items: center;
  background: var(--ground); border: 1px solid var(--accent); color: var(--accent);
  font-size: .74rem; font-weight: 700;
}
.steps li:last-child { border-left-color: transparent; padding-bottom: 0; }
.steps h3 { margin-bottom: .35rem; font-size: 1.02rem; }
.steps p { margin: 0; }
.band.paper .steps li::before { background: var(--paper); }
.band.paper .steps li { border-left-color: #ddd; }

/* ---------------------------------------------------------------- compare */

.compare { display: grid; gap: 1.25rem; margin-top: var(--pad); }
@media (min-width: 800px) { .compare { grid-template-columns: repeat(2, 1fr); } }
.compare > div { border-top: 2px solid var(--accent); background: var(--surface); padding: 1.9rem; }
.band.paper .compare > div { background: #fff; }
.compare-note {
  margin-top: 1.5rem; padding-left: 1.2rem; border-left: 2px solid var(--accent);
  color: var(--body); font-style: normal;
}
.band.paper .compare-note { color: var(--paper-body); }

/* ---------------------------------------------------------------- faq */

/* Bounded AND centred. A max-width with no auto inline margin left the block hard against the
   left edge of the container with a growing empty gutter beside it, which reads as a layout bug
   rather than a choice. */
.faq { margin-top: var(--pad); margin-inline: auto; max-width: 52rem; }
.faq details { border-bottom: 1px solid var(--surface-2); }
.faq summary {
  cursor: pointer; list-style: none; padding: 1.35rem 2.5rem 1.35rem 0; position: relative;
  color: var(--ink); font-weight: 600; font-size: 1.02rem; line-height: 1.4;
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary::after {
  content: "+"; position: absolute; right: 0; top: 1.15rem; color: var(--accent);
  font-size: 1.5rem; font-weight: 400; line-height: 1;
}
.faq details[open] summary::after { content: "\2013"; }
.faq .answer { padding: 0 0 1.5rem; }
.faq .answer p:last-child { margin-bottom: 0; }
.band.paper .faq summary { color: var(--paper-ink); }
.band.paper .faq details { border-bottom-color: #e2e2e2; }

/* ---------------------------------------------------------------- cta band */

.cta { background: var(--accent); color: #101010; }
.cta h2 { color: #101010; }
.cta p { color: #241a10; }
.cta .btn-primary { background: #101010; color: var(--accent); border-color: #101010; }
.cta .btn-primary:hover { background: #fff; border-color: #fff; color: #101010; }
.cta-inner { display: grid; gap: 1.6rem; align-items: center; }
@media (min-width: 900px) { .cta-inner { grid-template-columns: 1.4fr auto; } }

/* ---------------------------------------------------------------- lead form */

.leadform { margin-top: var(--pad); max-width: 42rem; }
/* The contact page's own form, centred under its centred heading. Not the hero form, which sits
   in a deliberate two-column composition beside the photograph and is already full-width there. */
.band > .wrap > .leadform { margin-inline: auto; }
.field { margin-bottom: 1.15rem; }
.field label {
  display: block; font-size: .76rem; font-weight: 600; letter-spacing: .1em;
  text-transform: uppercase; color: var(--muted); margin-bottom: .5rem;
}
.field input, .field textarea, .field select {
  width: 100%; background: var(--surface); border: 1px solid var(--surface-2);
  color: var(--ink); font: inherit; font-size: 1rem; padding: .9rem 1rem;
}
.field input:focus, .field textarea:focus, .field select:focus { border-color: var(--accent); }
.field textarea { min-height: 8rem; resize: vertical; }
.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }
.form-status { margin-top: 1rem; min-height: 1.5rem; }
.form-status[data-state="error"] { color: #ffb4a2; }
.form-status[data-state="ok"] { color: var(--accent); }

/* ---------------------------------------------------------------- footer */

.site-footer { background: var(--surface); border-top: 1px solid var(--surface-2); }
.footer-grid { display: grid; gap: 2.5rem; padding-block: var(--band) 2.5rem; }
@media (min-width: 800px) { .footer-grid { grid-template-columns: 1.4fr 1fr 1fr; } }
.site-footer h3 {
  font-size: .78rem; letter-spacing: .18em; text-transform: uppercase; color: var(--accent);
  margin-bottom: 1.1rem;
}
.site-footer ul { list-style: none; margin: 0; padding: 0; }
.site-footer li { margin-bottom: .2rem; }
/* Inline-block with real vertical padding, so a footer link is a comfortable tap target on a
   phone rather than a 19px line of text. */
.site-footer a { color: var(--body); display: inline-block; padding: .6rem 0; min-height: 44px; }
.site-footer a:hover { color: var(--accent); }
.footer-note { border-top: 1px solid var(--surface-2); padding-block: 1.6rem; font-size: .85rem; }
.footer-note p { color: var(--muted); }

/* ---------------------------------------------------------------- sticky mobile call */

.callbar {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 45; display: block;
  background: var(--accent); color: #101010; text-align: center; padding: .95rem 1rem;
  font-weight: 600; letter-spacing: .1em; text-transform: uppercase; font-size: .82rem;
  border: none;
}
.callbar:hover { color: #101010; background: var(--accent-deep); }
body { padding-bottom: 3.4rem; }
@media (min-width: 1140px) { .callbar { display: none; } body { padding-bottom: 0; } }

/* ---------------------------------------------------------------- breadcrumb + prose */

/* The probe measured this at 40x15 on every inner page at every width — the only standalone
   control on the site under the comfortable minimum. The bar keeps its height; the link grows
   into it. */
.crumbs { padding-block: .35rem; font-size: .8rem; color: var(--muted); }
.crumbs ol { list-style: none; display: flex; flex-wrap: wrap; align-items: center; gap: .5rem; margin: 0; padding: 0; }
.crumbs a { display: inline-flex; align-items: center; justify-content: center; min-height: 44px; min-width: 44px; }
.crumbs li { display: flex; align-items: center; }
.crumbs li + li::before { content: "/"; margin-right: .5rem; color: var(--surface-2); }
.crumbs a { color: var(--muted); }
.crumbs a:hover { color: var(--accent); }

.prose { max-width: var(--measure); }
.prose h2 { margin-bottom: 1rem; }

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; scroll-behavior: auto !important; }
  /* INVARIANT, and the reason it is stated HERE in the served stylesheet rather than only in the
     script: a reveal that holds content at opacity 0 until an observer fires leaves the page
     blank for a reader who asked for less motion, and render-probe.mjs captures with the
     preference emulated, so any element still transparent there is a frame that proves nothing.
     The script also declines to arm the reveals at all under this preference. Two halves, same
     refusal — this is the half a validator can read. */
  [data-reveal] { opacity: 1 !important; transform: none !important; }
  [data-carousel-track] { scroll-behavior: auto !important; }
  [data-carousel-autoplay] { animation: none !important; }
  [data-carousel-autoplay] [data-carousel-track] { scroll-behavior: auto !important; }
}

/* ---------------------------------------------------------------- mid-page call strip */

.callstrip { background: var(--surface-2); border-block: 1px solid var(--accent); }
.callstrip-inner {
  display: flex; flex-wrap: wrap; align-items: center; justify-content: space-between;
  gap: 1.2rem; padding-block: 1.6rem;
}
.callstrip p { margin: 0; color: var(--ink); font-weight: 500; max-width: 46ch; }


/* ---------------------------------------------------------------- hero lead form */

/* The homepage hero is positioning PLUS a real form. On desktop the two sit side by side so the
   photograph still reads; on a phone the copy comes first and the form follows it, because a form
   above the headline asks for details before saying what the business does. */
.hero.with-form .hero-inner { display: grid; gap: clamp(2rem, 4vw, 3.5rem); align-items: start; }
.hero.with-form .hero-copy { max-width: 36rem; }
@media (min-width: 900px) {
  .hero.with-form .hero-inner { grid-template-columns: 1.05fr 0.95fr; align-items: center; }
}

.hero-form {
  background: rgba(16, 16, 16, .82);
  border: 1px solid var(--surface-2);
  border-top: 2px solid var(--accent);
  padding: clamp(1.4rem, 3vw, 2rem);
  backdrop-filter: blur(4px);
  max-width: 34rem;
  width: 100%;
}
.hero-form-title { font-size: clamp(1.15rem, 2.2vw, 1.4rem); margin: 0 0 .35rem; }
.hero-form-note { color: var(--body); font-size: .92rem; margin: 0 0 1.25rem; max-width: 40ch; }
.hero-form .field { margin-bottom: .8rem; }
.hero-form .field label {
  font-size: .68rem; letter-spacing: .1em; margin-bottom: .35rem; color: #cfc9c2;
}
.hero-form .field input, .hero-form .field select, .hero-form .field textarea {
  padding: .72rem .85rem; font-size: .95rem; background: rgba(22, 22, 22, .92);
}
.hero-form .btn { width: 100%; text-align: center; margin-top: .35rem; }
.hero-form .form-status { font-size: .9rem; }

/* ---------------------------------------------------------------- footer brand + location */

.footer-brand { display: block; }
.footer-logo { height: 44px; width: auto; margin-bottom: 1.1rem; }
.footer-location { margin-top: 1.5rem; }
.footer-location h3 { margin-bottom: .55rem; }
.footer-location .locality { color: var(--ink); font-weight: 500; margin-bottom: .35rem; }
.footer-location .phone { font-size: 1.05rem; font-weight: 600; color: var(--accent); }
.footer-location .phone:hover { color: var(--ink); }
.footer-location p { margin-bottom: .35rem; }

/* ---------------------------------------------------------------- scroll reveals */

/* THE ONE SANCTIONED REVEAL. Nothing is hidden until the script has set data-reveal-armed on
   the document element, so a reader with no JavaScript gets the finished page and never the
   hidden state. The script refuses to arm under prefers-reduced-motion, and the safeguard in
   the reduced-motion block above says the same thing again where a validator can read it. */
[data-reveal-armed] [data-reveal] {
  opacity: 0;
  transform: translateY(16px);
  transition: opacity var(--t-slow) var(--ease-out), transform var(--t-slow) var(--ease-out);
  will-change: opacity, transform;
}
[data-reveal-armed] [data-reveal][data-revealed] {
  opacity: 1; transform: none; will-change: auto;
}

/* ---------------------------------------------------------------- services carousel */

/* project.ui_contracts.services_showcase: animated-carousel-v1.
   The track is a native scroll-snap container of real links; the controls are an addition, not
   the mechanism. No autoplay: a services showcase is a chooser, and a card that moves under a
   cursor reaching for it costs a reader the thing they came to do. */

.showcase { position: relative; margin-top: var(--pad); }

.showcase-nav { display: flex; justify-content: flex-end; gap: .6rem; margin-bottom: 1.1rem; }
.cbtn {
  width: 48px; height: 48px; display: grid; place-items: center;
  background: transparent; border: 1px solid var(--surface-2); color: var(--ink);
  font-size: 1.15rem; line-height: 1; cursor: pointer; padding: 0;
  transition: background var(--t-fast) var(--ease-out), border-color var(--t-fast) var(--ease-out),
    color var(--t-fast) var(--ease-out), transform var(--t-fast) var(--ease-out),
    opacity var(--t-fast) var(--ease-out);
}
.cbtn:active:not(:disabled) { transform: translateY(1px); }
.cbtn:disabled { opacity: .3; cursor: default; }
@media (hover: hover) and (pointer: fine) {
  .cbtn:hover:not(:disabled) { background: var(--accent); border-color: var(--accent); color: #101010; }
}

/* A NATIVE scroll container. With the script absent this still scrolls, still snaps, and still
   carries all five services to a reader and a crawler alike. */
/* Mechanics come from SiteKit's safeguards.css: display, overflow, snapping and the
   suspension of snapping while the track is drifting. Only spacing and chrome are declared
   here. `scroll-behavior: smooth` in particular must NOT be set — the canonical behaviour
   writes scrollLeft every frame, and a smooth scroll would try to animate toward each of
   those and turn a continuous drift into a fight. */
.showcase-track {
  gap: 1.25rem; list-style: none; margin: 0;
  padding: 0 0 .35rem;
  overscroll-behavior-x: contain;
  scrollbar-width: none; -ms-overflow-style: none;
}
.showcase-track::-webkit-scrollbar { display: none; }

/* STABLE DIMENSIONS, declared rather than inherited from content: cards that size themselves
   from their copy reflow the row as imagery arrives. The visible-card count is chosen per width,
   and every step deliberately leaves part of the next card showing — that sliver is the only
   honest affordance that the row continues. */
.showcase-track > * { flex: 0 0 86%; scroll-snap-align: start; }
@media (min-width: 560px)  { .showcase-track > * { flex-basis: 47%; } }
@media (min-width: 900px)  { .showcase-track > * { flex-basis: 38%; } }
@media (min-width: 1200px) { .showcase-track > * { flex-basis: 30.2%; } }

.scard {
  position: relative; display: flex; flex-direction: column;
  background: var(--surface); border: 1px solid var(--surface-2);
  box-shadow: var(--lift);
  transition: border-color var(--t-mid) var(--ease-out), transform var(--t-mid) var(--ease-out),
    box-shadow var(--t-mid) var(--ease-out);
}
.scard-media { overflow: hidden; background: var(--surface-2); }
.scard-media img {
  width: 100%; aspect-ratio: 4 / 3; object-fit: cover; display: block;
  transition: transform .6s var(--ease-out);
}
.scard-body { display: flex; flex-direction: column; flex: 1; padding: 1.6rem 1.7rem 1.8rem; }
.scard h3 { margin-bottom: .55rem; }
.scard h3 a { color: var(--ink); }
/* One link per card, and its target is the whole card. */
.scard h3 a::after { content: ""; position: absolute; inset: 0; }
.scard p { margin-bottom: 0; }
.scard:focus-within { border-color: var(--accent); outline: 3px solid var(--accent); outline-offset: 3px; }
.scard h3 a:focus-visible { outline: none; }
@media (hover: hover) and (pointer: fine) {
  .scard:hover { border-color: var(--accent); transform: translateY(-4px); box-shadow: var(--lift-hover); }
  .scard:hover .scard-media img { transform: scale(1.05); }
  .scard:hover h3 a { color: var(--accent); }
}

/* ---------------------------------------------------------------- prose composition */

/* Two of the homepage sections were a 68ch column of prose against the left edge with nearly
   half the band empty beside it. At desktop the heading moves into its own rail, which fills the
   width with structure instead of filler and gives the prose somewhere to start. */
@media (min-width: 1000px) {
  .prose {
    max-width: none;
    display: grid;
    grid-template-columns: minmax(0, 20rem) minmax(0, 1fr);
    column-gap: clamp(2.5rem, 5vw, 5rem);
  }
  .prose > h2 { grid-column: 1; grid-row: 1; margin: 0; }
  .prose > :not(h2) { grid-column: 2; }
  .prose > h2::after {
    content: ""; display: block; width: 56px; height: 2px; background: var(--accent);
    margin-top: 1.1rem;
  }
}

/* ---------------------------------------------------------------- daylight strip polish */

.daylight .n { font-variant-numeric: tabular-nums; }

/* ---------------------------------------------------------------- faq presence */

/* The accordion was hairlines on a flat ground, so an open panel did not read as opened. Each
   question becomes its own surface and the open one takes the camel edge. The control stays a
   native details/summary: nothing here scripts the disclosure. */
.faq details {
  background: var(--surface); border: 1px solid var(--surface-2);
  padding-inline: clamp(1.1rem, 3vw, 1.6rem); margin-bottom: .55rem;
  box-shadow: var(--lift);
  transition: border-color var(--t-mid) var(--ease-out);
}
.faq details:last-child { margin-bottom: 0; }
.faq details[open] { border-color: rgba(216, 171, 122, .5); }
.faq summary { transition: color var(--t-fast) var(--ease-out); }
/* A plus that rotates into a cross. The content swap it replaces could not animate at all, and
   an open that glides while the close snaps reads as a bug — this is symmetrical by construction. */
.faq summary::after {
  transition: transform var(--t-mid) var(--ease-out), color var(--t-fast) var(--ease-out);
  transform-origin: 50% 50%;
}
.faq details[open] summary::after { content: "+"; transform: rotate(45deg); }
@media (hover: hover) and (pointer: fine) {
  .faq summary:hover { color: var(--accent); }
}
.band.paper .faq details { background: #fff; border-color: #e2e2e2; box-shadow: 0 18px 40px -34px rgba(0, 0, 0, .5); }
.band.paper .faq details[open] { border-color: #cda274; }

/* ---------------------------------------------------------------- hero actions */

/* Two stacked buttons of different widths, one filled and one outlined, read as ragged on a
   phone. One column, equal width, hierarchy carried by fill rather than by size. */
@media (max-width: 559px) {
  .hero .btn-row { display: grid; grid-template-columns: 1fr; gap: .75rem; }
  .hero .btn-row .btn { text-align: center; }
}

/* ---------------------------------------------------------------- form states */

.field input, .field textarea, .field select {
  transition: border-color var(--t-fast) var(--ease-out), background var(--t-fast) var(--ease-out);
}
.field input:hover, .field textarea:hover, .field select:hover { border-color: #3a3a3a; }
.field input:focus, .field textarea:focus, .field select:focus { background: #1b1b1b; }

/* ---------------------------------------------------------------- surfaces */

.compare > div { box-shadow: var(--lift); }
.band.paper .compare > div { box-shadow: 0 18px 40px -34px rgba(0, 0, 0, .45); }

/* ================================================================ MOTION VOCABULARY
   Three moves, used everywhere, chosen from the brand rather than from a catalogue.
   DESIGN-DNA.md: exacting, unhurried, plainspoken; the visual metaphor is the floor
   itself — long grain running the length of a room, horizontal, layered, warm on dark.

     RISE    content arrives from below and settles. Grounded, never bouncy. Sections,
             cards, list rows, the hero column.
     SWEEP   a horizontal wipe along the grain: rules draw themselves left to right,
             a camel edge runs across the top of a card. This is the signature, and it
             is the metaphor made literal.
     LIFT    depth on hover and a press that answers. Surfaces, cards, buttons.

   Nothing else. Six unrelated effects read as six people working separately. */

/* ---------------------------------------------------------------- rise + stagger */

/* A group that staggers its children does NOT also fade as a whole — doing both makes
   every row cross two opacity ramps and the section arrives muddy instead of crisp. */
[data-reveal-armed] .grid[data-reveal],
[data-reveal-armed] .daylight[data-reveal],
[data-reveal-armed] .compare[data-reveal],
[data-reveal-armed] .steps[data-reveal],
[data-reveal-armed] .faq[data-reveal] {
  opacity: 1;
  transform: none;
}

[data-reveal-armed] .grid[data-reveal] > *,
[data-reveal-armed] .daylight[data-reveal] ol > li,
[data-reveal-armed] .compare[data-reveal] > div,
[data-reveal-armed] .steps[data-reveal] > li,
[data-reveal-armed] .faq[data-reveal] > details {
  opacity: 0;
  transform: translateY(18px);
  transition: opacity .62s var(--ease-out), transform .62s var(--ease-out);
}
[data-reveal-armed] .grid[data-reveal][data-revealed] > *,
[data-reveal-armed] .daylight[data-reveal][data-revealed] ol > li,
[data-reveal-armed] .compare[data-reveal][data-revealed] > div,
[data-reveal-armed] .steps[data-reveal][data-revealed] > li,
[data-reveal-armed] .faq[data-reveal][data-revealed] > details {
  opacity: 1;
  transform: none;
}

/* Hierarchy-aware: the eye is led down the list rather than shown all of it at once.
   70ms is the widest gap that still reads as one movement; wider reads as a queue. */
[data-reveal-armed] [data-reveal][data-revealed] > *:nth-child(1),
[data-reveal-armed] .daylight[data-reveal][data-revealed] ol > li:nth-child(1) { transition-delay: .02s; }
[data-reveal-armed] [data-reveal][data-revealed] > *:nth-child(2),
[data-reveal-armed] .daylight[data-reveal][data-revealed] ol > li:nth-child(2) { transition-delay: .09s; }
[data-reveal-armed] [data-reveal][data-revealed] > *:nth-child(3),
[data-reveal-armed] .daylight[data-reveal][data-revealed] ol > li:nth-child(3) { transition-delay: .16s; }
[data-reveal-armed] [data-reveal][data-revealed] > *:nth-child(4),
[data-reveal-armed] .daylight[data-reveal][data-revealed] ol > li:nth-child(4) { transition-delay: .23s; }
[data-reveal-armed] [data-reveal][data-revealed] > *:nth-child(5),
[data-reveal-armed] .daylight[data-reveal][data-revealed] ol > li:nth-child(5) { transition-delay: .30s; }
[data-reveal-armed] [data-reveal][data-revealed] > *:nth-child(n+6),
[data-reveal-armed] .daylight[data-reveal][data-revealed] ol > li:nth-child(n+6) { transition-delay: .36s; }

/* ---------------------------------------------------------------- sweep */

/* The section rule draws itself. It is 75x2px of camel and it is the one ornament this
   design source insists on, so it arrives rather than appearing. */
[data-reveal-armed] .stitle[data-reveal] { opacity: 1; transform: none; }
[data-reveal-armed] .stitle[data-reveal] > h2,
[data-reveal-armed] .stitle[data-reveal] > p {
  opacity: 0; transform: translateY(14px);
  transition: opacity .6s var(--ease-out), transform .6s var(--ease-out);
}
[data-reveal-armed] .stitle[data-reveal][data-revealed] > h2 { opacity: 1; transform: none; }
[data-reveal-armed] .stitle[data-reveal][data-revealed] > p { opacity: 1; transform: none; transition-delay: .1s; }
[data-reveal-armed] .stitle[data-reveal] h2::after {
  transform: scaleX(0);
  transition: transform .72s var(--ease-out) .18s;
}
[data-reveal-armed] .stitle[data-reveal][data-revealed] h2::after { transform: scaleX(1); }

/* The ghost word settles in behind the heading rather than being there already. */
[data-reveal-armed] .stitle[data-reveal][data-ghost]::before {
  opacity: 0; transform: translateY(-64%) translateX(-14px);
  transition: opacity 1.1s var(--ease-out) .1s, transform 1.1s var(--ease-out) .1s;
}
[data-reveal-armed] .stitle[data-reveal][data-revealed][data-ghost]::before {
  opacity: 1; transform: translateY(-64%);
}

@media (min-width: 1000px) {
  [data-reveal-armed] .prose[data-reveal] > h2::after {
    transform: scaleX(0); transform-origin: left;
    transition: transform .7s var(--ease-out) .2s;
  }
  [data-reveal-armed] .prose[data-reveal][data-revealed] > h2::after { transform: scaleX(1); }
}

/* ---------------------------------------------------------------- hero entrance */

/* One orchestrated arrival, in the order the eye should take it: label, headline, lede,
   actions, then the form. NOT [data-reveal] — the first screen must never depend on an
   observer, and the animation lands on the element's own base state, so the site's
   reduced-motion block switching animations off leaves everything visible immediately. */
@keyframes fm-rise { from { opacity: 0; transform: translateY(20px); } }
@keyframes fm-hero-settle { from { transform: scale(1.07); } }

.hero-copy .eyebrow { animation: fm-rise .7s var(--ease-out) .05s both; }
.hero-copy h1 { animation: fm-rise .8s var(--ease-out) .13s both; }
.hero-copy p.lede { animation: fm-rise .8s var(--ease-out) .21s both; }
.hero-copy .btn-row { animation: fm-rise .8s var(--ease-out) .29s both; }
.hero .hero-form { animation: fm-rise .9s var(--ease-out) .34s both; }
/* A photograph that settles rather than snapping. Long and slow: this is the one moment
   on the page allowed to take a second and a half. */
.hero-media img { animation: fm-hero-settle 1.8s var(--ease-out) both; }

/* Depth on scroll, at four percent of the distance. Any more and the hero detaches from
   the page; this is only meant to say the photograph sits behind the words. */
.hero-media { will-change: transform; }

/* ---------------------------------------------------------------- header on scroll */

.site-header { transition: background var(--t-mid) var(--ease-out), box-shadow var(--t-mid) var(--ease-out); }
.header-inner { transition: min-height var(--t-mid) var(--ease-out); }
[data-scrolled] .site-header {
  background: rgba(16, 16, 16, .99);
  box-shadow: 0 14px 34px -26px rgba(0, 0, 0, 1);
}
@media (min-width: 1140px) {
  [data-scrolled] .header-inner { min-height: 64px; }
}

/* ---------------------------------------------------------------- lift */

@media (hover: hover) and (pointer: fine) {
  .btn-primary:hover, .btn-ghost:hover { transform: translateY(-2px); }
  /* Declared after the hover rules so a press always wins over a hover lift. */
  .btn:active { transform: translateY(1px); }
  .callstrip .btn-primary:hover { transform: translateY(-2px); }
}

/* A camel edge runs across the top of a card as the pointer arrives — the sweep again,
   at component scale. */
.scard, .card.linked, .card.buildings-card { overflow: hidden; }
.scard::before, .card.linked::before, .card.buildings-card::before {
  content: ""; position: absolute; inset: 0 0 auto 0; height: 2px; z-index: 2;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform .45s var(--ease-out);
}
.card.linked, .card.buildings-card { position: relative; }
@media (hover: hover) and (pointer: fine) {
  .scard:hover::before, .card.linked:hover::before, .card.buildings-card:hover::before { transform: scaleX(1); }
  .card.buildings-card:hover { border-color: var(--accent); transform: translateY(-4px); box-shadow: var(--lift-hover); }
  .card.buildings-card:hover .card-media img { transform: scale(1.05); }
}
.scard:focus-within::before, .card.linked:focus-within::before { transform: scaleX(1); }

/* ---------------------------------------------------------------- carousel presence */

/* Edge fades. They tell a reader the row continues past the frame and stop a card being
   sliced off with a hard edge, which is what makes a scroller look like an accident.
   Pointer-events off so they never intercept a swipe or a click. */
/* Sized to the card's own inner padding (1.7rem) and no wider. A broad fade looks premium in
   isolation and dims the first card's heading, which is the thing the section exists to let
   somebody read. */
.showcase { --edge: clamp(1.1rem, 2.2vw, 1.75rem); }
.showcase::before, .showcase::after {
  content: ""; position: absolute; top: 0; bottom: 0; width: var(--edge); z-index: 3;
  pointer-events: none;
}
.showcase::before { left: 0; background: linear-gradient(90deg, var(--ground) 15%, rgba(16, 16, 16, 0)); }
.showcase::after { right: 0; background: linear-gradient(270deg, var(--ground) 15%, rgba(16, 16, 16, 0)); }

/* The drifting track must not also animate its own scroll position. */
.showcase-track { scroll-behavior: auto; }

/* ---------------------------------------------------------------- daylight strip */

/* The number is the spine of this section and was a 12px grey label. It now reads as a
   figure, with the camel hairline the design source uses everywhere else. */
.daylight .n {
  font-size: .95rem; letter-spacing: .04em; position: relative;
}
.daylight li { position: relative; }
.daylight li::after {
  content: ""; position: absolute; left: 0; bottom: -1px; height: 1px; width: 100%;
  background: var(--accent); transform: scaleX(0); transform-origin: left;
  transition: transform .5s var(--ease-out);
}
@media (hover: hover) and (pointer: fine) {
  .daylight li:hover::after { transform: scaleX(1); }
}

/* ---------------------------------------------------------------- cta band */

/* The closing band arrives with more weight than the sections above it: it is the last
   thing a reader passes and the one place the page asks for something. */
.cta { position: relative; overflow: hidden; }
[data-reveal-armed] .cta-inner[data-reveal] { transform: translateY(26px); }
[data-reveal-armed] .cta-inner[data-reveal][data-revealed] { transform: none; }

/* ---------------------------------------------------------------- faq */

/* Open and close as a PAIR, or not at all. The marker rotation and the surface change
   both run in reverse on close; a content animation would only play on open, and an
   open that glides while the close snaps reads as a bug. */
.faq details { transition: border-color var(--t-mid) var(--ease-out), background var(--t-mid) var(--ease-out); }
.faq details[open] { background: #191919; }
.band.paper .faq details[open] { background: #fff; }

/* ---------------------------------------------------------------- footer map */

/* The frame ships with no src and SiteKit sets it only when a reader approaches, so the box
   has to hold its own height from the first paint or the footer reflows the moment it loads.
   A dark surface behind it, so the empty frame reads as a panel rather than as a gap. */
.footer-map {
  margin: 1.1rem 0 0;
  border: 1px solid var(--surface-2);
  background: var(--ground);
  box-shadow: var(--lift);
  overflow: hidden;
  transition: border-color var(--t-mid) var(--ease-out);
}
.footer-map iframe { display: block; width: 100%; height: 220px; border: 0; }
@media (hover: hover) and (pointer: fine) {
  .footer-map:hover { border-color: var(--accent); }
}
