/* Landing page. Tokens and shared components live in base.css. */

.page-home {
  --rule-inset: var(--gutter); /* gap between a hairline and what sits beside it, matching .listing-grid */
  --frame-x: max(var(--gutter), calc((100% - var(--maxw)) / 2 + var(--gutter))); /* page content edge */
}

/* Three columns that line up exactly with the .rules hairlines. */

.thirds {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
}

/* A full-bleed frame that can narrow to the page's content edge. landing.js scrubs --c from 0 to 1. */

.framed {
  --c: 0;
  clip-path: inset(
    0 calc(var(--frame-x) * var(--c)) 0 calc(var(--frame-x) * var(--c))
    round calc(var(--radius) * var(--c))
  );
}

/* A line of type that rises from behind its own edge. The padding keeps descenders from being cut. */

.mask {
  display: block;
  overflow: hidden;
  padding-bottom: 0.24em;
  margin-bottom: -0.24em;
  padding-left: 0.05em;
  margin-left: -0.05em;
}

.mask > span { display: block; white-space: nowrap; }

/* Pull large type left by its side bearing, so its stems line up with the small text below. */

.chapter__title .mask { margin-left: -0.12em; }
.hero__title .mask { margin-left: -0.08em; }

/* Some eyebrows here are section headings. */

h2.eyebrow { font-weight: 400; }

/* The accent focus ring is too dim against dark imagery. */

.hero :focus-visible,
.site-header.is-on-dark :focus-visible { outline-color: var(--on-dark); }

/* Without JS, motion.js never updates the header, so it scrolls away with the white text over the hero. */

html:not(.js) .site-header { position: absolute; color: var(--on-dark); }

/* Hero */

.hero {
  position: relative;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  height: 100vh;
  height: 100svh;
  min-height: 22rem;
  color: var(--on-dark);
}

.hero__frame {
  position: absolute;
  inset: 0;
  overflow: hidden;
  background: var(--ink);
}

.hero__frame::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background:
    radial-gradient(78% 64% at 0% 100%, rgba(18, 14, 10, 0.34) 0%, rgba(18, 14, 10, 0.26) 55%, rgba(18, 14, 10, 0) 100%),
    linear-gradient(to top, rgba(18, 14, 10, 0.72) 0%, rgba(18, 14, 10, 0.5) 26%, rgba(18, 14, 10, 0.2) 40%, rgba(18, 14, 10, 0) 56%),
    linear-gradient(to bottom, rgba(18, 14, 10, 0.5) 0%, rgba(18, 14, 10, 0.28) 8%, rgba(18, 14, 10, 0) 18%);
}

.hero__plate {
  position: absolute;
  inset: 0;
  animation: plate-settle 1.8s var(--ease-out) both;
}

/* Taller than the frame, so the picture can drift down as the page scrolls without exposing an edge. */

.hero__picture { position: absolute; inset: -12% 0 0; }

.hero__picture img,
.hero__video {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  max-width: none;
  object-fit: cover;
}

.hero__video { opacity: 0; transition: opacity 0.8s linear; }
.hero__video.is-playing { opacity: 1; }

.hero__content {
  position: relative;
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  align-items: end;
  width: 100%;
  max-width: var(--maxw);
  margin-inline: auto;
  padding-bottom: clamp(24px, 3.2vw, 44px);
}

.hero__title {
  grid-area: 1 / 1 / 1 / -1;
  padding-inline: var(--gutter);
  font-size: min(10.6vw, 10.25rem); /* keeps the second line clear of the button in the third column */
  line-height: 1;
}

.hero__title .mask > span {
  animation: line-rise 1.15s var(--ease-out) both;
  animation-delay: calc(0.08s + var(--i, 0) * 0.09s);
}

.hero__side {
  grid-area: 1 / 3;
  padding-inline: var(--rule-inset) var(--gutter);
  animation: hero-side 0.5s var(--ease-out) 0.05s both;
}

.hero__actions { display: flex; align-items: center; gap: 12px; }

/* Says there is more below, because the hero fills the screen. It nudges a few times, then rests. */

.hero__cue {
  display: grid;
  place-items: center;
  flex: none;
  width: 56px;
  height: 56px;
  border: 1px solid currentColor;
  border-radius: 50%;
  transition: background-color 0.25s linear, color 0.25s linear, border-color 0.25s linear;
}

.hero__cue svg { animation: cue-nudge 1.5s ease-in-out 1.2s 3; }

.hero__cue:hover { background: var(--on-dark); border-color: var(--on-dark); color: var(--ink); }

@keyframes cue-nudge {
  50% { transform: translateY(4px); }
}

@keyframes line-rise {
  from { transform: translateY(112%); }
  to { transform: none; }
}

@keyframes hero-side {
  from { opacity: 0; transform: translateY(16px); }
  to { opacity: 1; transform: none; }
}

@keyframes plate-settle {
  from { transform: scale(1.06); }
  to { transform: none; }
}

/* Statement */

/* Opaque, so the hairlines don't run through this large type. */

.statement { padding-block: var(--section-y); background: var(--paper); }

/* The label sits right above the words it names. */

.statement__label {
  grid-column: 2;
  justify-self: start;
  margin: 0 0 clamp(14px, 1.4vw, 20px) var(--rule-inset);
}

.statement__text {
  grid-column: 2 / -1;
  padding-inline: var(--rule-inset) var(--gutter);
  text-wrap: pretty;
}

/* Process. The markup's resting state is a plain stack of stills, used without JS and with reduced motion. */

.process { background: var(--ink); color: var(--on-dark); }

.process__canvas,
.process__scrim,
.process__progress { display: none; }

.process:not(.process--live) .process__stage { padding-block: var(--section-y); clip-path: none; }

.process__label { margin-inline: var(--gutter); color: var(--on-dark-2); }

.process__chapters { display: block; margin-top: clamp(32px, 4vw, 64px); }

.chapter {
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  grid-template-rows: auto 1fr;
  row-gap: 16px;
  padding-block: clamp(24px, 3vw, 40px);
  border-top: 1px solid var(--line-dark);
}

.chapter__title { grid-area: 1 / 1; padding-left: var(--gutter); }
.chapter__title.display-xl { font-size: var(--display-m); line-height: 1; }
.process:not(.process--live) .chapter__title .mask > span { white-space: normal; }
.chapter__body { grid-area: 2 / 1; padding-inline: var(--gutter) var(--rule-inset); font-size: var(--text-lg); line-height: 1.32; letter-spacing: -0.015em; }

.chapter__media {
  grid-area: 1 / 2 / 3 / -1;
  padding-inline: var(--rule-inset) var(--gutter);
}

.chapter__media img { width: 100%; border-radius: var(--radius); background: rgba(255, 255, 255, 0.06); }

/* Process, live: a sticky stage scrubbed by landing.js, inside a section tall enough to scroll through. */

.process--live { height: 420vh; background: none; }

.process--live .process__stage {
  position: sticky;
  top: 0;
  height: 100vh;
  height: 100lvh;
  overflow: hidden;
  background: var(--ink);
}

.process--live .process__canvas {
  display: block;
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
}

.process--live .process__scrim {
  display: block;
  position: absolute;
  inset: 0;
  /* The corner glow sits behind the big title, so the rest of the picture can stay bright. */
  background:
    radial-gradient(78% 64% at 0% 100%, rgba(18, 14, 10, 0.46) 0%, rgba(18, 14, 10, 0.36) 55%, rgba(18, 14, 10, 0) 100%),
    linear-gradient(to top, rgba(18, 14, 10, 0.78) 0%, rgba(18, 14, 10, 0.58) 24%, rgba(18, 14, 10, 0.26) 36%, rgba(18, 14, 10, 0) 52%),
    linear-gradient(to bottom, rgba(18, 14, 10, 0.58) 0%, rgba(18, 14, 10, 0.42) 8%, rgba(18, 14, 10, 0) 18%);
}

/* The canvas fills the large viewport so a collapsing mobile address bar never exposes an edge.
   The text stays inside the small viewport so it is never hidden behind that bar. */

.process--live .process__ui {
  position: absolute;
  inset: 0 0 auto;
  height: 100vh;
  height: 100svh;
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
}

/* The stage speaks for itself, so its heading is for screen readers only here. */

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

.process--live .process__chapters { position: relative; margin-block: 0; }

.process--live .chapter {
  position: absolute;
  inset: auto 0 clamp(20px, 2.4vw, 32px);
  grid-template-rows: auto;
  align-items: end;
  padding: 0;
  border: 0;
  row-gap: 0;
  pointer-events: none;
}

.process--live .chapter__media { display: none; }

.process--live .chapter__title { grid-area: 1 / 1 / 1 / 3; }
.process--live .chapter__title.display-xl { font-size: clamp(3rem, 8vw, 7.75rem); } /* the longest line fills the first two columns */

.process--live .chapter__title .mask > span {
  transform: translateY(112%);
  transition: transform 0.95s var(--ease-out);
}

.process--live .chapter.is-past .chapter__title .mask > span { transform: translateY(-112%); }
.process--live .chapter.is-active .chapter__title .mask > span { transform: none; transition-delay: calc(0.08s + var(--i, 0) * 0.07s); }

.process--live .chapter__body {
  grid-area: 1 / 3;
  padding-inline: var(--rule-inset) var(--gutter);
  padding-bottom: 0.4em;
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.35s linear, transform 0.35s linear;
}

.process--live .chapter.is-active .chapter__body {
  opacity: 1;
  transform: none;
  transition: opacity 0.7s linear 0.18s, transform 0.95s var(--ease-out) 0.18s;
}

.process--live .process__progress {
  display: grid;
  padding-bottom: clamp(4px, 0.8vw, 12px);
}

/* Shortcuts to each service as well as progress, each tall enough to tap. */

.tick {
  display: block;
  min-height: 44px;
  padding-block: 10px 14px;
  color: var(--on-dark-2);
  text-align: left;
  transition: color 0.3s linear;
}

.tick:hover { color: var(--on-dark); }

.process :focus-visible { outline-color: var(--on-dark); }
.tick.is-active { color: var(--on-dark); font-weight: 500; }
.tick:nth-child(1) { padding-inline: var(--gutter) var(--rule-inset); }
.tick:nth-child(2) { padding-inline: var(--rule-inset); }
.tick:nth-child(3) { padding-inline: var(--rule-inset) var(--gutter); }

.tick__bar {
  display: block;
  height: 1px;
  overflow: hidden;
  background: var(--line-dark);
}

.tick__bar i {
  display: block;
  height: 100%;
  background: var(--on-dark);
  transform: scaleX(0);
  transform-origin: left;
}

.tick__label {
  display: block;
  margin-top: 10px;
  font-size: var(--text-sm);
  line-height: 1.2;
}

.tick__label b { margin-right: 6px; font-weight: 400; opacity: 0.75; }

/* Philosophy */

.philosophy { padding-block: var(--section-y); }

.philosophy__head { background: var(--paper); }

/* Always one line, so on phones the size follows the width. The divisor is the line's width in ems. */

.philosophy__title {
  grid-column: 1 / -1;
  padding-inline: var(--gutter);
  font-size: min(var(--section-title), calc((100vw - 2 * var(--gutter)) / 9.4));
  font-weight: 300;
  white-space: nowrap;
}

.philosophy__items { margin-top: clamp(32px, 4vw, 56px); }

.philosophy__item:nth-child(1) { padding-inline: var(--gutter) var(--rule-inset); }
.philosophy__item:nth-child(2) { padding-inline: var(--rule-inset); }
.philosophy__item:nth-child(3) { padding-inline: var(--rule-inset) var(--gutter); }

/* Each column starts lower than the last. */

@media (min-width: 901px) {
  .philosophy__item:nth-child(2) { margin-top: clamp(28px, 3.5vw, 56px); }
  .philosophy__item:nth-child(3) { margin-top: clamp(56px, 7vw, 112px); }
}

.philosophy__fig .craft__img { aspect-ratio: 4 / 5; }

.philosophy__item h3 { margin-top: 20px; }
.philosophy__item p { margin-top: 12px; }

/* Picture frames, shared by the philosophy columns and the gallery. */

.craft__img {
  position: relative;
  overflow: hidden;
  border-radius: var(--radius);
  background: var(--paper-2);
}

/* Taller than the frame, so landing.js can drift the picture inside it. */

.craft__img img {
  position: absolute;
  top: -8%;
  left: 0;
  width: 100%;
  height: 116%;
  max-width: none;
  object-fit: cover;
}

.craft__fig figcaption { margin-top: 10px; }

/* Gallery. On its own it is a row to swipe or scroll sideways, used on phones, with reduced
   motion and without JavaScript. On larger screens landing.js turns it into a pinned section
   (.craft--live) that moves sideways as the page scrolls down. */

.craft {
  padding-block: var(--section-y);
  background: var(--paper-2);
}

.craft__track {
  display: grid;
  grid-auto-flow: column;
  grid-auto-columns: max-content;
  grid-template-rows: auto auto;
  gap: clamp(24px, 3vw, 40px) clamp(12px, 2vw, 32px);
  padding-inline: var(--gutter);
  overflow-x: auto;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scroll-padding-inline: var(--gutter);
  scrollbar-width: none;
}

.craft__track::-webkit-scrollbar { display: none; }

/* The heading stays put above the row while the pictures slide under it. */

.craft__head {
  grid-row: 1;
  grid-column: 1 / span 4;
  position: sticky;
  left: 0;
  width: min(calc(100vw - 2 * var(--gutter)), 34rem);
}

.craft__head h2 { margin-top: 14px; font-size: var(--section-title); font-weight: 300; text-wrap: pretty; }

.craft__fig {
  grid-row: 2;
  scroll-snap-align: center;
}

.craft__fig .craft__img {
  width: min(78vw, 36rem);
  aspect-ratio: 4 / 5;
  background: var(--line);
}

/* These frames travel sideways, so their pictures are wider than the frame rather than taller. */

.craft__fig .craft__img img {
  top: 0;
  left: -8%;
  width: 116%;
  height: 100%;
}

/* This frame is portrait and the still is wide, so keep the table in the middle of the crop. */
.craft__fig--dining .craft__img img { object-position: 75% 50%; }

.craft__meter {
  display: flex;
  align-items: center;
  gap: 14px;
  margin: 20px var(--gutter) 0;
  font-size: var(--text-sm);
  color: var(--ink-3);
}

.craft__meter[hidden] { display: none; }

.craft__bar {
  flex: 0 1 14rem;
  height: 2px;
  overflow: hidden;
  background: var(--line-strong);
}

.craft__bar i {
  display: block;
  height: 100%;
  background: var(--ink);
  transform: scaleX(0.25);
  transform-origin: left;
}

/* Shown only on touch screens, and only until the visitor has swiped once. */

.craft__hint { display: none; }

@media (hover: none) {
  .craft__meter:not(.is-used) .craft__hint { display: inline; }
}

.craft--live { padding-block: 0; }

.craft--live .craft__stage {
  position: sticky;
  top: 0;
  display: flex;
  align-items: center;
  height: 100vh;
  overflow: hidden;
}

.craft--live .craft__track {
  display: flex;
  align-items: center;
  width: max-content;
  height: 100%;
  padding-block: calc(var(--header-h) + 8px) 64px;
  overflow: visible;
  gap: clamp(32px, 4.5vw, 80px);
  will-change: transform;
}

.craft--live .craft__head {
  position: static;
  flex: none;
  width: min(30vw, 34rem);
}

.craft--live .craft__meter {
  position: absolute;
  inset: auto var(--frame-x) 20px;
  margin: 0;
}

@media (max-height: 640px) {
  .craft--live .craft__meter { display: none; }
}

.craft--live .craft__fig { flex: none; }
.craft--live .craft__fig--counter { align-self: flex-end; }
.craft--live .craft__fig--patio { align-self: flex-start; }

.craft--live .craft__fig .craft__img { width: auto; }
.craft--live .craft__fig--kitchen .craft__img { height: 72vh; aspect-ratio: 16 / 10; }
.craft--live .craft__fig--counter .craft__img { height: 48vh; aspect-ratio: 1 / 1; }
.craft--live .craft__fig--patio .craft__img { height: 62vh; aspect-ratio: 16 / 10; }
.craft--live .craft__fig--dining .craft__img { height: 74vh; aspect-ratio: 4 / 5; }

/* Listings preview */

.preview { padding-block: var(--section-y); }

/* Sized to stay inside the centre column, between the hairlines, as on the listings page. */

.preview__title { text-align: center; font-size: var(--section-title); font-weight: 300; }

.preview .listing-grid { margin-top: clamp(32px, 4vw, 56px); }

.preview .sample-note { margin-top: clamp(24px, 3vw, 40px); text-align: center; }

.preview__more { display: flex; justify-content: center; margin-top: clamp(24px, 3vw, 40px); }

/* FAQ */

.faq { padding-block: 0 var(--section-y); }

.faq__title { grid-column: 1; padding-inline: var(--gutter) var(--rule-inset); font-size: var(--section-title); font-weight: 300; line-height: 1.02; letter-spacing: -0.045em; }

.faq__list { grid-column: 2 / -1; padding-inline: var(--rule-inset) var(--gutter); }

.faq__item { border-top: 1px solid var(--line); }
.faq__item:last-child { border-bottom: 1px solid var(--line); }

.faq__question {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 24px;
  padding-block: clamp(18px, 2vw, 28px);
  font-size: clamp(1.25rem, 2vw, 1.75rem);
  line-height: 1.2;
  letter-spacing: -0.025em;
  cursor: pointer;
  list-style: none;
  -webkit-tap-highlight-color: transparent;
  transition: padding-bottom 0.45s var(--ease-out);
}

/* An open answer sits close under its own question. */

.faq__item[open]:not(.is-closing) .faq__question { padding-bottom: clamp(8px, 1vw, 14px); }

.faq__question::-webkit-details-marker { display: none; }

.faq__question > span { transition: transform 0.6s var(--ease-out); }

/* A plus in a ring. The ring fills on hover and while the answer is open, and the plus closes to a minus. */

.faq__icon {
  position: relative;
  flex: none;
  width: 40px;
  height: 40px;
  border: 1px solid var(--line-strong);
  border-radius: 50%;
  transition: background-color 0.35s linear, border-color 0.35s linear, color 0.35s linear, transform 0.15s var(--ease-out);
}

.faq__icon::before,
.faq__icon::after {
  content: "";
  position: absolute;
  inset: 50% 12px auto;
  height: 1px;
  background: currentColor;
  transition: transform 0.5s var(--ease-out);
}

.faq__icon::after { transform: rotate(90deg); }

.faq__item[open]:not(.is-closing) .faq__icon {
  background: var(--ink);
  border-color: var(--ink);
  color: var(--paper);
}

.faq__item[open]:not(.is-closing) .faq__icon::after { transform: none; }

.faq__question:active .faq__icon { transform: scale(0.92); }

@media (hover: hover) and (pointer: fine) {
  .faq__question:hover > span { transform: translateX(8px); }
  .faq__question:hover .faq__icon { background: var(--ink); border-color: var(--ink); color: var(--paper); }
}

/* Height is animated by landing.js, so the padding belongs to the paragraph inside. */

.faq__answer { overflow: hidden; }

.faq__answer p {
  max-width: 38em;
  padding-bottom: clamp(20px, 2.4vw, 32px);
  font-size: var(--text-lg);
  line-height: 1.45;
  letter-spacing: -0.012em;
  color: var(--ink-2);
  text-wrap: pretty;
}

/* Anchors should land with the full-bleed section at the very top, not offset for the header. */

#services { scroll-margin-top: calc(var(--header-h) * -1); }

/* Phones and small tablets: the hairlines are off, so everything is one column. */

@media (max-width: 900px) {
  .hero__content { display: block; padding-inline: var(--gutter); }

  /* "Crafted for you." fills the column, and "Timeless interiors." wraps onto two lines above it.
     The divisor is that line's width in ems. Never too tall for a landscape phone. */
  .hero__title { padding-inline: 0; font-size: min(calc((100vw - 2 * var(--gutter)) / 5.9), 19svh); }
  .hero__title .mask > span { white-space: normal; }
  .hero__side { padding-inline: 0; margin-top: 16px; }

  .statement .thirds { display: block; padding-inline: var(--gutter); }
  .statement__label { margin: 0 0 16px; }
  .statement__text { padding-inline: 0; }

  .chapter { display: block; padding-inline: var(--gutter); }
  .chapter__title { padding: 0; }
  .chapter__body { padding: 0; margin-top: 12px; }
  .chapter__media { padding: 0; margin-bottom: 16px; }

  .process--live .chapter { display: block; padding-inline: var(--gutter); inset: auto 0 16px; }
  /* "Project Management" fills the column. The divisor is that line's width in ems. */
  .process--live .chapter__title.display-xl { font-size: min(calc((100vw - 2 * var(--gutter)) / 7.9), 12svh); }
  .process--live .chapter__body { padding: 0; margin-top: 10px; }

  .process__progress.thirds { padding-inline: var(--gutter); gap: 8px; }
  .tick:nth-child(n) { padding-inline: 0; }

  .philosophy .thirds { display: block; padding-inline: var(--gutter); }
  .philosophy__title { padding-inline: 0; }
  .philosophy__item:nth-child(n) { padding-inline: 0; }
  .philosophy__item + .philosophy__item { margin-top: 40px; }

  .faq .thirds { display: block; padding-inline: var(--gutter); }
  .faq__title { padding-inline: 0; }
  .faq__list { padding-inline: 0; margin-top: 32px; }

}

/* Three long service names won't fit side by side, and the current one is on screen above anyway. */

@media (max-width: 640px) {
  .tick__name { display: none; }
}

/* On a landscape phone the type fills most of the height, so the scrim has to as well. */

@media (max-height: 500px) {
  .hero__frame::after { background: rgba(0, 0, 0, 0.5); }
}

@media (prefers-reduced-motion: reduce) {
  .hero__plate,
  .hero__title .mask > span,
  .hero__side { animation: none; }
}
