/* Ateclier — landing page
   Brand Manual Edition 1.1 — "Drawing paper and brass". tokens.css is not
   present in the repo, so the brand system is declared here as custom
   properties. C1-build-spec.md still describes the Edition 1.0 palette. */

:root {
  /* Colour */
  --prussian-ink:    #14303C;  /* type and linework on light grounds */
  --blueprint-paper: #E3E8EA;  /* light anchor */
  --deep-prussian:   #0D1F27;  /* dark anchor — dark section grounds only */
  --brass:           #B08A2E;  /* accent as fill; never as text */
  --brass-shade:     #7F6014;  /* brass as text — the only brass for type */
  --brass-button:    #A67F22;  /* the Web-and-UI button fill, one step darker */
  --gold-on-dark:    #D9B45E;  /* the C and the dot on dark ground */
  --pale-blue:       #C3D0D4;

  /* The hairline ink on its own, so rules drawn as backgrounds and rules
     drawn as borders come from one source. */
  --hairline-ink:        rgba(20, 48, 60, 0.14);
  --hairline:            1px solid var(--hairline-ink);
  --hairline-inverse:    1px solid rgba(227, 232, 234, 0.14);
  /* The inverse ink on its own. --hairline-inverse is a border shorthand
     and cannot be used where a bare colour is wanted, as the drawn rules
     below need it. */
  --hairline-ink-invert: rgba(227, 232, 234, 0.14);

  /* Mapped from the migration table. Nothing draws at 28% yet — the Method
     table still uses --hairline-ink, as it did in Edition 1.0. */
  --rule-ink: rgba(20, 48, 60, 0.28);

  /* Type */
  --serif: "Vollkorn", Georgia, serif;
  --sans:  "DM Sans", system-ui, sans-serif;
  --pixel: "Silkscreen", monospace;

  /* Height of the sticky header: 24px padding block, twice, plus the 40px
     lockup. Anchor targets clear it via scroll-padding-top, so this has to
     stay in step with .site-header__inner's padding — see the 719px query.
     Measured height is 89px: the bottom hairline adds the odd pixel. The
     difference is absorbed by the 104px of padding above every section
     label, so 88 is kept as the nominal figure. */
  --header-h: 88px;
}

/* ---------- Base ---------- */

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

/* scroll-padding-top keeps anchor targets below the sticky header instead of
   behind it. It applies to the smooth scroll and to the instant jump the
   reduced-motion override below falls back to. */
html {
  scroll-behavior: smooth;
  scroll-padding-top: var(--header-h);
}

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

body {
  margin: 0;
  background: var(--blueprint-paper);
  color: var(--prussian-ink);
  font-family: var(--sans);
  font-weight: 400;
  font-size: 16px;
  line-height: 1.65;
}

a {
  color: var(--prussian-ink);
  text-underline-offset: 0.2em;
  text-decoration-thickness: 1px;
}

/* Brass as text takes its shade, never the base. */
a:hover { color: var(--brass-shade); }

/* Body copy links: the underline sweeps in from the left instead of being
   there all along. Same gesture as the hairlines at 240ms rather than
   1100ms, because this is feedback, not an entrance.

   Scoped by position: the nav and the lockup live in <header>, so `main a`
   misses them, and the CTA is excluded by name. Today that leaves the two
   Work card domains. currentColor keeps the bar on whatever the link is —
   Prussian at rest, brass shade on hover, paper on a dark ground.

   A link that wraps gets the bar under one fragment only, which is why
   this is scoped to short standalone links rather than inline prose. */
main a:not(.cta) {
  position: relative;
  text-decoration: none;
}

main a:not(.cta)::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: -0.2em;              /* where text-underline-offset put it */
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform 240ms cubic-bezier(.22,.61,.36,1);
}

/* :focus-visible as well as :hover — without it a keyboard reader loses
   the affordance entirely. */
main a:not(.cta):hover::after,
main a:not(.cta):focus-visible::after {
  transform: scaleX(1);
}

@media (prefers-reduced-motion: reduce) {
  /* Keep the bar, drop the sweep: it still appears on hover and focus,
     just without travelling. */
  main a:not(.cta)::after { transition: none; }
}

:focus-visible {
  outline: 2px solid var(--prussian-ink);
  outline-offset: 2px;
}

img,
svg { display: block; }

p,
h1,
h2,
h3 { margin: 0; }

/* ---------- Container and sections ---------- */

.container {
  max-width: 1280px;
  margin-inline: auto;
  padding-inline: 24px;
}

.section {
  padding-block: 104px;
  border-top: var(--hairline);
}

.section--hero { border-top: 0; }

/* Deep Prussian ground: contact and footer only. The colour change is the
   divider. Deep Prussian is the dark ground; Prussian ink is for type. */
.section--dark {
  background: var(--deep-prussian);
  color: var(--blueprint-paper);
  border-top: 0;
}

/* A section following a dark one drops its top border too: the colour
   change is the divider at both edges, and the light hairline would
   otherwise sit against the dark block's bottom edge. */
.section--dark + .section { border-top: 0; }

/* The dark ground recolours body links only. The CTA is a button and
   owns its own colours, so it is excluded rather than out-specified. */
.section--dark a:not(.cta) { color: var(--blueprint-paper); }
.section--dark a:not(.cta):hover { color: var(--pale-blue); }

/* The Prussian focus ring vanishes on deep Prussian; paper is the ground's ink. */
.section--dark :focus-visible,
.site-footer :focus-visible { outline-color: var(--blueprint-paper); }

/* ---------- Type ---------- */

.display {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.25rem, 1.6rem + 2.6vw, 3.25rem);
  line-height: 1.1;
  letter-spacing: 0.01em;
  margin: 0;
}

.heading {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.1875rem, 1.05rem + 0.7vw, 1.75rem);
  line-height: 1.25;
  max-width: 34ch;
  margin: 0;
}

/* Section intro paragraphs: heading size, looser leading, body measure. */
.intro {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.1875rem, 1.05rem + 0.7vw, 1.75rem);
  line-height: 1.35;
  max-width: 68ch;
}

.subhead {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 19px;
  line-height: 1.25;
}

.card-name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 26px;
  line-height: 1.1;
}

.lead {
  font-size: 19px;
  line-height: 1.6;
}

.label {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
}

/* Section labels are brass. As small text that means the shade on paper
   grounds and gold on deep Prussian — the brass base is never type at any
   size. The descendant rule has to out-specify nothing in particular: a
   colour set here beats the ground colour .section--dark inherits down, so
   the dark case needs its own rule to win back. */
.section-label { color: var(--brass-shade); }
.section--dark .section-label { color: var(--gold-on-dark); }

/* Silkscreen stage numbers in Process and the Method table. Small brass
   text: the shade, never the base. */
.num {
  font-family: var(--pixel);
  font-weight: 700;
  font-size: 11px;
  color: var(--brass-shade);
}

.body { max-width: 68ch; }

/* ---------- Header ---------- */

/* Sticky. The background has to be declared rather than inherited from the
   body: content scrolls underneath, and an inherited background paints
   nothing. z-index 10 keeps it above the statement band's absolute layers
   and the Process figure. */
.site-header {
  position: sticky;
  top: 0;
  z-index: 10;
  background: var(--blueprint-paper);
  border-bottom: var(--hairline);
}

.site-header__inner {
  display: flex;
  justify-content: space-between;
  align-items: center;
  flex-wrap: wrap;
  gap: 24px;
  padding-block: 24px;
}

.lockup-link { display: inline-flex; }

.site-nav {
  display: flex;
  flex-wrap: wrap;
  gap: 24px;
}

/* Below 719px the header is not one height: the nav wraps, and how far it
   wraps depends on the width. Tighter padding throughout, then --header-h
   re-stated per measured band. All four values are measured, not derived —
   they track where the four nav items actually wrap, so they need
   re-measuring if the nav's items or labels change.

       >= 720px   89px   nav beside the lockup, 24px padding
    567-719px     73px   nav still beside the lockup, 16px padding
    346-566px    115px   nav wrapped onto its own line
       <= 345px   157px   nav wrapped onto two lines

   Values below are rounded up to the whole pixel so a target never lands
   under the header. */
@media (max-width: 719px) {
  :root { --header-h: 73px; }

  .site-header__inner { padding-block: 16px; }
}

@media (max-width: 566px) {
  :root { --header-h: 116px; }
}

@media (max-width: 345px) {
  :root { --header-h: 158px; }
}

.site-nav a {
  font-family: var(--sans);
  font-weight: 500;
  font-size: 11px;
  letter-spacing: 0.18em;
  text-transform: uppercase;
  text-decoration: none;
  transition: color 200ms ease;
}

/* The section currently occupying the top of the viewport. Applied by the
   nav observer, so with JavaScript off it never appears and the nav is
   simply Prussian throughout. Brass shade, not the base: 11px caps is
   small text. */
.site-nav a.is-current { color: var(--brass-shade); }

/* ---------- Section heads ---------- */

.section-head {
  display: grid;
  gap: 24px;
}

.section-head__titles {
  display: grid;
  gap: 24px;
}

/* Intro paragraphs here run at body size so the heading stays dominant, and
   are capped at the 68ch measure since the head is now full width. */
.section-head__intro > p {
  font-size: 16px;
  line-height: 1.65;
  max-width: 68ch;
}

/* ---------- The problem ---------- */

/* Crumpled paper behind the passage, same construction as the statement
   band. The veil is lighter there — 0.74 against 0.78 — so the creases
   still read. The section's own deep Prussian stays the fallback. */
.section--dark#problem {
  position: relative;
  overflow: hidden;
  background: var(--deep-prussian);
}

.problem__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.problem__veil {
  position: absolute;
  inset: 0;
  background: rgba(13, 31, 39, 0.74);
}

/* Lifts the text above image and veil. No z-index needed: all three are
   painted in tree order and the inner block comes last. */
.problem__inner {
  position: relative;
}

/* The section label, same as PROCESS / METHOD / WORK / CONTACT. Still an
   h2 so the section keeps a heading; .label supplies the appearance and
   .section-label the brass. */
.problem__title {
  margin-bottom: 40px;
}

/* Vollkorn like the statement band, sized down for a longer passage.
   Blueprint paper on deep Prussian is inherited from .section--dark.
   Its top margin collapses with the title's 40px bottom margin, so the gap
   between them is 40px, not 80px. */
.problem__body {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(1.5rem, 1.1rem + 1.6vw, 2.25rem);
  line-height: 1.35;
  max-width: 48ch;
  margin-top: 40px;
}

/* ---------- Hero ---------- */

/* Two columns: text left, mark right, vertically centred. The 104px gap and
   the section's 104px block padding hold the mark's required clear space —
   nothing else may enter it. */
.hero__inner {
  display: grid;
  grid-template-columns: minmax(0, 1fr) auto;
  gap: 104px;
  align-items: center;
}

.hero__title { max-width: 20ch; }

.hero__support {
  margin: 40px 0 0;
  max-width: 68ch;
}

/* Full construction, used at its own stroke widths. Sits directly on the
   paper: no background, border or container. */
.hero__mark {
  width: 240px;
  height: 240px;
}

@media (max-width: 719px) {
  .hero__inner {
    grid-template-columns: minmax(0, 1fr);
    gap: 64px;
    justify-items: start;
  }

  .hero__mark {
    width: 160px;
    height: 160px;
  }
}

/* ---------- Process (Variant B) ---------- */

/* Stages left, photograph right. The 0.62fr right column keeps the stage
   bodies near their 56ch measure at 1280px; if the text column ever feels
   cramped, reduce it to 0.55fr rather than touching the measure — and
   change the img's sizes to match, or the browser picks the wrong file. */
.process__split {
  display: grid;
  grid-template-columns: minmax(0, 1fr) minmax(0, 0.62fr);
  gap: 64px;
  margin-top: 64px;
  align-items: start;
}

/* The split now owns the gap below the section head, so .stages no longer
   carries its own top margin — it would add to the split's 64px, not
   collapse into it, and push the stages to 104px. */
.stages {
  display: grid;
  gap: 40px;
}

.process__figure { margin: 0; }

/* Full strength on blueprint paper — unlike the statement band this image
   is meant to be looked at. No border, panel or shadow; 2px is the
   maximum radius the manual allows. */
.process__figure img {
  width: 100%;
  height: auto;
  display: block;
  border-radius: 2px;
}

.stage {
  display: grid;
  grid-template-columns: 72px minmax(0, 1fr);
  gap: 24px;
  padding-top: 24px;
  border-top: var(--hairline);
}

.stage__label {
  display: flex;
  align-items: baseline;
  gap: 8px;
}

.stage__name {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 19px;
  line-height: 1.25;
}

.stage__body {
  max-width: 56ch;
  margin-top: 8px;
}

@media (max-width: 719px) {
  /* One column, image below the stages: the stages are the content. */
  .process__split {
    grid-template-columns: minmax(0, 1fr);
    gap: 40px;
  }

  .stage {
    grid-template-columns: minmax(0, 1fr);
    gap: 16px;
  }
}

/* ---------- Statement band ---------- */

/* One sentence on deep Prussian, nothing else. 160px is deliberately off the
   8/16/24/40/64/104 scale so the band reads taller than every other
   section. Paper colour is inherited from .section--dark. */
.section--statement {
  padding-block: 160px;
  position: relative;
  overflow: hidden;
  background: var(--deep-prussian);   /* the fallback if the image fails */
}

/* Weathered plaster behind the sentence (manual, 14 · Imagery). The file is
   already graded to the palette, so nothing is applied on top of it here. */
.statement__img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

/* Deep Prussian at 78%. The same value as the section's background, so a
   failed image load reads as a plain dark band rather than a broken one.
   0.70 shows more wall, 0.86 shows less; this is the only value to touch. */
.statement__veil {
  position: absolute;
  inset: 0;
  background: rgba(13, 31, 39, 0.78);
}

/* Lifts the text above image and veil. No z-index needed: all three are
   painted in tree order and the inner block comes last. */
.statement__inner {
  position: relative;
}

.statement {
  font-family: var(--serif);
  font-weight: 400;
  font-size: clamp(2.75rem, 2rem + 3.5vw, 4.5rem);
  line-height: 1.15;
  max-width: 22ch;
}

/* ---------- Method ---------- */

/* The intro is a grid item in the split head now, so it carries no margin. */

/* Tight to the intro: the two are one lead-in, not two statements. */
.method__free { margin: 24px 0 0; }

.method__lead-in { margin-top: 40px; }

/* Ruled index table. The 1px gaps show the container's background as the
   rules and the border closes the frame, so no cell draws its own. */
.method-areas {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1px;
  background: var(--hairline-ink);
  border: 1px solid var(--hairline-ink);
  border-radius: 2px;
  /* Tighter than the 40px above the lead-in, so the lead-in binds to the
     table it introduces rather than to the paragraph above it. */
  margin-top: 24px;
}

.method-area {
  display: grid;
  gap: 8px;
  align-content: start;
  background: var(--blueprint-paper);
  padding: 32px;
}

.method-area__title {
  font-family: var(--serif);
  font-weight: 400;
  font-size: 19px;
  line-height: 1.25;
  margin: 0;
}

/* No measure cap on the cell bodies: the cell is narrower than any cap
   worth writing, so the cell is the measure. */

@media (max-width: 719px) {
  /* One column. The gaps still show through as the rules between rows. */
  .method-areas { grid-template-columns: minmax(0, 1fr); }
  .method-area { padding: 24px; }
}

.method-limits {
  margin-top: 104px;
  border-top: var(--hairline);
  padding-top: 40px;
}

.method-limits__body {
  max-width: 68ch;
  margin-top: 24px;
}

/* ---------- Work ---------- */

.work__intro { margin-top: 40px; }

.work-cards {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(280px, 1fr));
  gap: 24px;
  margin-top: 64px;
}

.work-card {
  display: grid;
  gap: 40px;
  align-content: start;
  border: var(--hairline);
  border-radius: 2px;
  padding: 40px;
}

.work-card__name {
  display: flex;
  align-items: center;
  height: 72px;
  border-bottom: var(--hairline);
  padding-bottom: 24px;
}

.work-card__body { max-width: 40ch; }

.work-card__meta {
  display: grid;
  gap: 8px;
}

/* A status label, and brass is the accent. Small text, so the shade. */
.work-card__stage { color: var(--brass-shade); }

/* ---------- Contact ---------- */

.contact__title {
  max-width: 18ch;
  margin-top: 24px;
}

.contact__body { margin: 40px 0 0; }

.contact__free { margin: 24px 0 0; }

/* The primary brass button. Brass cannot carry white text at any size, so
   the label is deep Prussian; the hover shade is dark enough for paper. */
.cta {
  display: inline-block;
  margin-top: 40px;
  font-weight: 500;
  line-height: 1;
  padding: 16px 24px;
  border-radius: 2px;
  border: 1px solid var(--brass-button);
  background: var(--brass-button);
  color: var(--deep-prussian);
  text-decoration: none;
}

.cta:hover {
  background: var(--brass-shade);
  border-color: var(--brass-shade);
  color: var(--blueprint-paper);
}

/* ---------- Footer ---------- */

.site-footer {
  background: var(--deep-prussian);
  color: var(--blueprint-paper);
  border-top: var(--hairline-inverse);
  padding-block: 64px;
}

.site-footer__inner {
  display: grid;
  gap: 24px;
  justify-items: start;
}

.site-footer__lines {
  display: grid;
  gap: 8px;
}

.site-footer a { color: var(--blueprint-paper); }
.site-footer a:hover { color: var(--pale-blue); }

/* ---------- Drawing animation ---------- */

/* The mark and the stage icons draw themselves as they enter view: strokes
   trace in, then fills follow. Every stroked element carries pathLength="1"
   and .dr, every filled one .fl, so a dasharray of 1 hides any stroke
   whatever its real geometry and all of them finish together. */

/* Resting state: fully drawn. This is what everyone sees if JavaScript
   never runs, so the page is correct without it. */
.draw .dr { stroke-dasharray: 1; stroke-dashoffset: 0; }
.draw .fl { opacity: 1; }

/* Armed by JS only. Nothing is hidden until we know we can reveal it.

   transition: none here, so re-arming snaps. Without it the armed state is
   reached by animating backwards over the full duration, and an element
   that returns to view before that finishes resumes from wherever it got
   to — a 0.85-to-1 sliver that reads as no draw at all. Snap out, animate
   in: the transition on the rules below applies only when .is-drawn is
   present, which is the direction worth watching. */
.js .draw:not(.is-drawn) .dr { stroke-dashoffset: 1; transition: none; }
.js .draw:not(.is-drawn) .fl { opacity: 0; transition: none; }

/* Half pace: 1800ms a stroke on a 240ms stagger.

   The fill has to start later for an icon with more strokes, or the
   relationship between the two drifts with complexity — a two-stroke icon
   would finish drawing long before its fill arrived, a five-stroke one long
   after. --fill-delay carries that per icon so all five read alike: the
   fill begins 160ms before the last stroke lands, whatever the count.

   The .draw--N class states the icon's stroke count and has to be kept in
   step with the markup, like the :nth-child stagger below. */
.draw {
  --fill-delay: 1000ms;
}

.draw--2 { --fill-delay: 1880ms; }   /* last stroke ends 2040ms */
.draw--3 { --fill-delay: 2120ms; }   /* ends 2280ms */
.draw--4 { --fill-delay: 2360ms; }   /* ends 2520ms */
.draw--5 { --fill-delay: 2600ms; }   /* ends 2760ms */

.draw .dr {
  transition: stroke-dashoffset 1800ms cubic-bezier(.22,.61,.36,1);
}
.draw .fl {
  transition: opacity 1200ms ease var(--fill-delay);   /* fills follow the strokes */
}

/* Stagger the strokes so it reads as drawing, not switching on.
   Note :nth-child counts ALL siblings, not only the .dr ones — the index is
   the element's position inside the <svg>, whatever its class. It lands
   correctly on the five icons as they are written today, but reordering
   elements inside an icon, or inserting one, silently reshuffles the timing
   rather than failing visibly. Check the order before moving anything. */
.draw .dr:nth-child(2) { transition-delay:   0ms; }
.draw .dr:nth-child(3) { transition-delay: 240ms; }
.draw .dr:nth-child(4) { transition-delay: 480ms; }
.draw .dr:nth-child(5) { transition-delay: 720ms; }
.draw .dr:nth-child(6) { transition-delay: 960ms; }

@media (prefers-reduced-motion: reduce) {
  .js .draw .dr { stroke-dashoffset: 0 !important; }
  .js .draw .fl { opacity: 1 !important; }
  .draw .dr, .draw .fl { transition: none !important; }
}

/* ---------- Hairlines draw ---------- */

/* Every horizontal rule traces left to right as it enters view. A 1px
   pseudo-element scaled from scaleX(0) with a left origin: compositor work
   only, no layout. 1100ms — slower than a UI transition, faster than the
   1800ms icon draw, so the rules stay the quieter gesture.

   Resting state is drawn, so the page is correct with JavaScript off. */
.rule { position: relative; }

.rule::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  height: 1px;
  background: var(--hairline-ink);
  transform: scaleX(1);
  transform-origin: left center;
  transition: transform 1100ms cubic-bezier(.22,.61,.36,1);
}

/* Over whichever border it replaces */
.section.rule::before,
.stage.rule::before,
.method-limits.rule::before,
.site-footer.rule::before { top: -1px; }
.work-card__name.rule::before { bottom: -1px; }

/* Hide the real border so it doesn't show while the line is drawing */
.rule { border-top-color: transparent; }
.work-card__name.rule { border-bottom-color: transparent; }

/* Most sections carry no top hairline at all — the hero has none, a dark
   section drops its own, and the section after a dark one drops its too.
   Only Work has one. Mirror those three suppressions here, or the
   pseudo-element invents six rules the page has never had. */
.section--hero.rule::before,
.section--dark.rule::before,
.section--dark + .section.rule::before { content: none; }

/* On dark grounds the hairline inverts. After the suppressions above the
   footer is the only one left, but the descendant selector stays so a rule
   placed inside a dark section later inverts without further thought. */
.section--dark .rule::before,
.site-footer.rule::before { background: var(--hairline-ink-invert); }

/* Armed by JS only. transition: none so re-arming snaps rather than
   animating backwards — see the note on the icons above. */
.js .rule:not(.is-drawn)::before {
  transform: scaleX(0);
  transition: none;
}

/* The Method table's rules are 1px grid gaps showing the container through,
   so there is nothing to scale. They fade in together instead — a different
   gesture in the same family. */
.method-areas {
  transition: background-color 900ms ease, border-color 900ms ease;
}
.js .method-areas:not(.is-drawn) {
  background-color: transparent;
  border-color: transparent;
  transition: none;
}

@media (prefers-reduced-motion: reduce) {
  .js .rule::before { transform: scaleX(1) !important; }
  /* --hairline-ink, not --rule-ink: the table draws its gaps at 14% and
     forcing 28% here would darken it for reduced-motion readers only. */
  .js .method-areas {
    background-color: var(--hairline-ink) !important;
    border-color: var(--hairline-ink) !important;
  }
  .rule::before, .method-areas { transition: none !important; }
}
