/* ============================================================
   BOMBAY HAIR PATCH — shared stylesheet for the secondary pages
   (blog index and articles). index.html is still self-contained.

   WHAT THIS REPLACED, AND WHY
   The previous style.css was a different design system altogether —
   Playfair Display and DM Sans, a gold accent (#C2903F), a film-grain
   overlay, radial gold glows, and components (.stat, .tier, .sys,
   .craft, .voice, .map) that do not exist on the live site. Used as
   written, the blog would have looked like a different company.

   It also broke two of the project's hard rules: a third and fourth
   typeface, and a second accent hue. And the specific combination it
   reached for — Playfair + warm-clay accent + film grain + big stat
   numbers — is the most recognisable generated-design cluster there is.

   TOKENS ARE MIRRORED FROM index.html AND MUST MATCH IT.
   index.html is the source of truth; its :root block carries the
   measured contrast ratios. Change a value there and change it here.
   Migrating index.html onto this file would remove the duplication and
   is on the fix list in README.md — it is deliberately not done yet,
   because index.html is the LCP page and works today.
   ============================================================ */
:root{
  /* Tells the browser to draw its OWN chrome dark: scrollbars, native form
     popups, autofill. index.css has carried this since the guidelines audit;
     style.css never did, so these pages rendered a light scrollbar down the
     side of a near-black page. Measured live: getComputedStyle gave "dark" on
     the home page and "normal" on /services. */
  color-scheme: dark;

  --ink:        #151110;
  --ink-2:      #1E1815;
  --ink-3:      #2A211C;
  --stone:      #E8E1D7;
  --stone-2:    #D9D0C3;
  --bone:       #F6F2EC;
  --muted:      #A89C93;   /* 7.00:1 on --ink  (AAA) */
  --muted-dk:   #50463F;   /* 7.07:1 on --stone (AAA) */
  --line:       rgba(246,242,236,.14);  /* decorative only */
  --line-dk:    rgba(21,17,16,.16);     /* decorative only */
  --line-ui:    #716860;   /* real control boundaries, 3.22:1 */
  --line-ui-dk: #867B6A;
  --wa:         #25D366;   /* the only hue, WhatsApp only */

  /* COPPER — academy.html only, and the brief for that page asked for it by
     name ("subtle copper accent"). It is allowed to exist because it never
     touches the customer journey: no page a client reads uses it. Keep it
     that way, or the palette has two accents instead of one job each.

     6.00:1 on --ink, so it is safe at text size for the 01–08 numerals and
     not merely for large display type. */
  --copper:     #C08552;

  --display: "Bodoni Moda", "Didot", "Times New Roman", serif;
  --text:    "Archivo", "Helvetica Neue", Arial, sans-serif;

  /* THE BOTTOM THREE STEPS ARE FLUID NOW, AND THAT WAS THE BUG.
     --s2 upward have always been clamp() with a vw term, so headings grew
     with the viewport. These three did not — they were fixed rem — so every
     line of body copy on the site stayed at the same pixel size whether you
     were on a 1080 laptop or a 27" monitor, while the headline beside it grew
     to 110px. That mismatch is why the copy kept reading as "too small": it
     was not small, it was static while everything around it scaled.

     EACH ONE IS TUNED TO BE IDENTICAL AT 1080px AND BELOW. Substitute 1080
     into any of the three and you get exactly the old value, so the phone
     layouts, the 1080 nav fit and every measured line-length are untouched.
     Growth only starts above that. At 1920 the body step reaches ~19px.

     Measures are in ch throughout, so they scale with the type and the line
     length stays where it was set. */
  --s-1: clamp(.78rem, .66rem + .18vw, .92rem);
  --s0:  clamp(.95rem, .80rem + .23vw, 1.12rem);
  --s1:  clamp(1.05rem, .88rem + .26vw, 1.25rem);
  --s2:  clamp(1.35rem, 1.1rem + .9vw, 1.9rem);
  --s3:  clamp(1.9rem, 1.3rem + 2.4vw, 3.1rem);
  --s4:  clamp(2.6rem, 1.4rem + 4.6vw, 5.4rem);

  --pad:  clamp(1.25rem, 5vw, 5.5rem);
  --gap:  clamp(1.5rem, 3vw, 3rem);
  --max:  1680px;
  --ease: cubic-bezier(.22,.61,.36,1);
  --nav-h: 4.6rem;
  --scrim:   rgba(21,17,16,.82);
  --veil:    rgba(246,242,236,.06);
  --bone-hi: #fff;
  --shadow-lg: rgba(0,0,0,.55);   /* mirrors index.css — raised panels */
}


/* ============================================================
   LIGHT THEME — mirrored from index.css, and it must stay mirrored.
   Same palette inverted; not one new colour. .on-light means "the
   alternate surface", so in this theme it is the dark band.
   --line-ui is #796F60 rather than a straight swap because the straight
   swap measured 2.72:1 against the light surface and 1.4.11 needs 3.0.
   ============================================================ */
:root[data-theme="light"]{
  color-scheme: light;      /* see the note in :root */
  --ink:        #E8E1D7;
  --ink-2:      #D9D0C3;
  --ink-3:      #CEC4B5;
  --stone:      #151110;
  --stone-2:    #1E1815;
  --bone:       #151110;
  --muted:      #50463F;
  --muted-dk:   #A89C93;
  --line:       rgba(21,17,16,.16);
  --line-dk:    rgba(246,242,236,.14);
  --line-ui:    #796F60;
  --line-ui-dk: #716860;
  /* Copper has to darken when the page turns cream. #C08552 measures 2.41:1
     against --ink in this theme — an outright failure, not a near miss. This
     is 5.19:1. Same accent, same job, different surface. */
  --copper:     #7E5226;
  --shadow-lg: rgba(21,17,16,.20);
  --scrim:   rgba(232,225,215,.86);
  --veil:    rgba(21,17,16,.05);
  --bone-hi: #000;
}
body,.site-head,.section,.post,.btn{
  transition:background-color .45s var(--ease), color .45s var(--ease),
             border-color .45s var(--ease);
}
@media(prefers-reduced-motion:reduce){
  body,.site-head,.section,.post,.btn{transition:none}
}

*,*::before,*::after{box-sizing:border-box}
html{-webkit-text-size-adjust:100%; scroll-behavior:smooth}
body{
  margin:0; background:var(--ink); color:var(--bone);
  font-family:var(--text); font-weight:300; font-size:var(--s1);
  line-height:1.62; letter-spacing:.005em;
  -webkit-font-smoothing:antialiased; overflow-x:hidden;
  /* Same fix as index.html: browsers map Bodoni Moda's optical-size axis
     to font-size, and at display sizes that picks opsz≈96, where the
     hairlines thin to 1px and letters break up. Archivo has no opsz axis
     and ignores this. */
  font-optical-sizing:none;
  font-variation-settings:"opsz" 22;
}
img{display:block; max-width:100%; height:auto}
a{color:inherit; text-decoration:none}
button{font:inherit; color:inherit; background:none; border:0; cursor:pointer}
:focus-visible{outline:2px solid var(--bone); outline-offset:4px; border-radius:2px}
::selection{background:var(--bone); color:var(--ink)}

h1,h2,h3{margin:0; font-weight:400; font-family:var(--display); letter-spacing:-.015em; color:var(--bone)}
h1{font-size:var(--s4); line-height:1.04}
h2{font-size:var(--s3); line-height:1.1}
h3{font-size:var(--s2); line-height:1.2}
p{margin:0 0 1.1em}
p:last-child{margin-bottom:0}

.wrap{max-width:var(--max); margin:0 auto; padding-inline:var(--pad)}
.section{padding-block:clamp(3.5rem, 8vh, 7rem)}
.rule{border-top:1px solid var(--line)}
.body{max-width:62ch; color:var(--muted)}
.small{font-size:var(--s-1); color:var(--muted)}

/* Skip link — same contract as index.html: hidden by transform so it stays
   focusable, and <main> takes tabindex="-1" so focus actually moves. */
.skip{
  position:fixed; top:0; left:0; z-index:100;
  padding:.85rem 1.35rem; background:var(--bone); color:var(--ink);
  font-size:var(--s0); border-radius:0 0 2px 0;
  transform:translateY(-120%); transition:transform .2s var(--ease);
}
.skip:focus{transform:translateY(0)}
main:focus{outline:none}

/* ---- buttons ------------------------------------------------ */
.btn{
  display:inline-flex; align-items:center; gap:.65rem;
  padding:1.05rem 1.85rem; border-radius:999px;
  font-size:var(--s0); font-weight:400; letter-spacing:.01em;
  border:1px solid transparent;
  transition:background .45s var(--ease), color .45s var(--ease), border-color .45s var(--ease);
}
.btn-solid{background:var(--bone); color:var(--ink)}
.btn-solid:hover{background:var(--bone-hi)}
/* --line-ui, not --line: a ghost button's border is the control boundary */
.btn-ghost{border-color:var(--line-ui); color:var(--bone)}
.btn-ghost:hover{border-color:var(--bone); background:var(--veil)}
.btn-sm{padding:.72rem 1.3rem; font-size:var(--s-1)}
.dot{width:7px; height:7px; border-radius:50%; background:var(--wa); flex:none}

.ul{position:relative; padding-bottom:2px}
.ul::after{content:""; position:absolute; left:0; right:0; bottom:0; height:1px;
  background:currentColor; transform:scaleX(0); transform-origin:right;
  transition:transform .5s var(--ease)}
.ul:hover::after{transform:scaleX(1); transform-origin:left}

/* ---- header ------------------------------------------------- */
.site-head{
  position:fixed; inset:0 0 auto 0; z-index:80;
  display:flex; align-items:center; justify-content:space-between; gap:1rem;
  padding:1.15rem var(--pad);
  background:var(--scrim);
  -webkit-backdrop-filter:blur(16px); backdrop-filter:blur(16px);
  border-bottom:1px solid var(--line);
}
.brand{display:flex; flex-direction:column; line-height:1}
.brand b{font-family:var(--display); font-weight:400; font-size:1.22rem; letter-spacing:.01em}
.brand i{font-style:normal; font-size:.63rem; letter-spacing:.16em; color:var(--muted); margin-top:.42rem}
/* Mirrors .nav-links in index.css — see the note there. --s0 was fine print
   for a primary nav; --s1 with a tighter gap keeps the row the same width. */
.site-nav{display:none; gap:1.7rem; font-size:var(--s1); color:var(--muted)}
.site-nav a{position:relative}
.site-nav a::after{
  content:""; position:absolute; left:0; right:0; bottom:-5px; height:1px;
  background:currentColor; transform:scaleX(0); transform-origin:right;
  transition:transform .45s var(--ease);
}
.site-nav a:hover::after{transform:scaleX(1); transform-origin:left}
.site-nav a[aria-current]{color:var(--bone)}
.site-nav a[aria-current]::after{transform:scaleX(1)}
.site-nav a{transition:color .35s var(--ease)}
.site-nav a:hover,.site-nav a[aria-current="page"]{color:var(--bone)}
.head-actions{display:flex; align-items:center; gap:.9rem}
.theme-btn{
  display:inline-flex; align-items:center; justify-content:center;
  width:38px; height:38px; border-radius:999px; flex:none;
  border:1px solid var(--line-ui); color:var(--bone);
  transition:border-color .35s var(--ease), background .35s var(--ease);
}
.theme-btn:hover{border-color:var(--bone); background:var(--veil)}
.theme-btn svg{width:17px; height:17px; display:block}
.theme-btn .icon-dark{display:none}
:root[data-theme="light"] .theme-btn .icon-dark{display:block}
:root[data-theme="light"] .theme-btn .icon-light{display:none}
@media(min-width:1080px){ .site-nav{display:flex} }

/* ---- blog index --------------------------------------------- */
.page-head{padding-top:calc(var(--nav-h) + clamp(3rem,8vh,6rem))}
.lede{font-size:var(--s2); line-height:1.42; font-weight:300; max-width:46ch; color:var(--muted); margin-top:1.6rem}

/* 1px gaps over --line, the same device index.html uses for .steps,
   .macro, .quotes and the room triptych. A new one would be decoration. */
.posts{display:grid; gap:1px; background:var(--line); border-block:1px solid var(--line)}
@media(min-width:760px){ .posts{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media(min-width:1200px){ .posts{grid-template-columns:repeat(3,minmax(0,1fr))} }
.post{background:var(--ink); padding:clamp(1.6rem,3vw,2.4rem); display:flex; flex-direction:column}
.post h2{font-size:var(--s2); line-height:1.22; margin-bottom:.7rem}
.post h2 a{transition:opacity .4s var(--ease)}
.post h2 a:hover{opacity:.6}
.post p{font-size:var(--s0); color:var(--muted); margin:0}
.post .when{margin-top:auto; padding-top:1.4rem; font-size:var(--s-1); color:var(--muted)}
.post--soon h2{color:var(--muted)}

/* ---- article ------------------------------------------------ */
.article{max-width:68ch; margin-inline:auto}
.article h1{margin-bottom:1.4rem}
.article h2{margin:2.8rem 0 1rem; font-size:var(--s2)}
.article p,.article li{color:var(--muted); font-size:var(--s1)}
.article strong{color:var(--bone); font-weight:400}
.article ul{margin:0 0 1.1em; padding-left:1.1rem}
.article li{margin-bottom:.6rem}
.article .meta{font-size:var(--s-1); color:var(--muted); margin-bottom:2.4rem;
  padding-bottom:1.2rem; border-bottom:1px solid var(--line)}

.article table{width:100%; border-collapse:collapse; margin:0 0 1.6rem; font-size:var(--s0)}
.article th,.article td{text-align:left; padding:.85rem .9rem; border-bottom:1px solid var(--line)}
.article th{color:var(--bone); font-weight:400; border-bottom-color:var(--line-ui)}
.article td{color:var(--muted)}
/* wide tables must scroll inside themselves, never the page */
.table-scroll{overflow-x:auto; -webkit-overflow-scrolling:touch}

.callout{
  border-left:2px solid var(--line-ui); background:var(--ink-2);
  padding:clamp(1.2rem,3vw,1.8rem); margin:2rem 0; border-radius:2px;
}
.callout p{margin:0; font-size:var(--s0)}

/* ---- footer + mobile bar ------------------------------------ */
.site-foot{padding-block:clamp(3rem,7vh,5rem); border-top:1px solid var(--line)}
.foot-top{display:grid; gap:var(--gap)}
@media(min-width:760px){ .foot-top{grid-template-columns:minmax(0,1fr) auto} }
.foot-links{display:flex; flex-wrap:wrap; gap:.8rem 1.8rem; font-size:var(--s0); color:var(--muted)}
.foot-legal{display:flex; flex-wrap:wrap; gap:.8rem 2rem; justify-content:space-between;
  margin-top:2.4rem; padding-top:1.5rem; border-top:1px solid var(--line);
  font-size:var(--s-1); color:var(--muted)}

.mobile-bar{
  position:fixed; left:0; right:0; bottom:0; z-index:75;
  display:grid; grid-template-columns:repeat(3,1fr); gap:1px;
  background:var(--line); border-top:1px solid var(--line);
  padding-bottom:env(safe-area-inset-bottom);
}
.mobile-bar a{display:flex; align-items:center; justify-content:center; gap:.5rem;
  padding:1.05rem .5rem; font-size:var(--s0); background:var(--ink-2)}
.mobile-bar a.primary{background:var(--bone); color:var(--ink)}
@media(min-width:1080px){ .mobile-bar{display:none} }
@media(max-width:1079px){ body{padding-bottom:4.2rem} }

@media(prefers-reduced-motion:reduce){
  html{scroll-behavior:auto}
  *,*::before,*::after{animation-duration:.01ms !important; transition-duration:.01ms !important}
}

/* ---- services glossary --------------------------------------
   A numbered list, because it IS a list of things and the numbers are the
   only chrome it gets. Two columns from 900px so fifteen entries do not read
   as an endless scroll, and the same 1px-gap-over---line device the rest of
   the site uses for a row of related panels. */
.svc{list-style:none; margin:0; padding:0;
  display:grid; gap:1px; background:var(--line);
  border-block:1px solid var(--line)}
@media(min-width:900px){ .svc{grid-template-columns:repeat(2,minmax(0,1fr))} }
.svc-item{background:var(--ink); padding:clamp(1.5rem,3vw,2.2rem); counter-increment:svc}
.svc-item h2{
  font-family:var(--text); font-weight:400; font-size:var(--s1);
  line-height:1.35; letter-spacing:normal; margin:0 0 .6rem;
  display:flex; gap:.8rem; align-items:baseline;
}
/* the number is generated, so the markup stays a clean ordered list and the
   count can never drift from the content */
.svc-item h2::before{
  content:counter(svc,decimal-leading-zero);
  font-family:var(--display); font-size:var(--s-1); color:var(--muted);
  flex:none; letter-spacing:.04em;
}
.svc{counter-reset:svc}
.svc-item p{margin:0; font-size:var(--s0); color:var(--muted); max-width:52ch}


/* ============================================================
   SERVICES DROPDOWN

   Progressive enhancement: the markup ships a plain link to services.html
   and menu.js swaps in the button. With scripting off the link still works,
   so the fifteen services are never unreachable.

   Desktop: a card anchored under the button, right-aligned so it cannot run
   off the viewport. Phone: a full-width sheet hung under the fixed header,
   because a 420px dropdown on a 390px screen is just a worse full screen.
   On a phone it also carries the page links — below 1080px .site-nav and
   .nav-links are display:none and the blog and services pages have no
   burger, so without this those pages had no navigation at all.
   ============================================================ */
.svcmenu{position:relative}
.svcmenu-link{font-size:var(--s0); color:var(--muted)}
.svcmenu-link:hover{color:var(--bone)}

.svcmenu-btn{
  display:inline-flex; align-items:center; gap:.5rem;
  padding:.72rem 1.15rem; border-radius:999px;
  border:1px solid var(--line-ui);          /* functional border: WCAG 1.4.11 */
  color:var(--bone); font-size:var(--s-1); letter-spacing:.01em;
  white-space:nowrap;
  transition:border-color .35s var(--ease), background .35s var(--ease);
}
.svcmenu-btn:hover{border-color:var(--bone); background:var(--veil)}
.svcmenu-btn svg{width:10px; height:10px; flex:none;
  transition:transform .35s var(--ease)}
.svcmenu[data-open] .svcmenu-btn{border-color:var(--bone); background:var(--veil)}
.svcmenu[data-open] .svcmenu-btn svg{transform:rotate(180deg)}

.svcmenu-panel{
  position:absolute; z-index:95; right:0; top:calc(100% + .7rem);
  width:min(420px, calc(100vw - 2rem));
  max-height:min(70vh, 560px); overflow-y:auto;
  background:var(--ink-2); border:1px solid var(--line-ui); border-radius:3px;
  box-shadow:0 24px 60px var(--shadow-lg);
  /* fade and rise, the same entrance the rest of the site uses */
  opacity:0; transform:translateY(-8px);
  transition:opacity .3s var(--ease), transform .3s var(--ease);
}
.svcmenu[data-open] .svcmenu-panel{opacity:1; transform:translateY(0)}

.svcmenu-list{list-style:none; margin:0; padding:.4rem}
.svcmenu-item + .svcmenu-item{border-top:1px solid var(--line)}
.svcmenu-term{
  width:100%; text-align:left; display:flex; align-items:baseline; gap:.7rem;
  padding:.85rem .8rem; font-size:var(--s0); line-height:1.35;
  color:var(--bone); border-radius:2px;
  transition:background .25s var(--ease);
}
.svcmenu-term:hover{background:var(--ink-3)}
/* the +/- is generated, so the markup carries no decorative glyphs for a
   screen reader to read out — aria-expanded already says the same thing */
.svcmenu-term::before{
  content:"+"; flex:none; width:1em; color:var(--muted); line-height:1;
}
.svcmenu-term[aria-expanded="true"]{color:var(--bone); background:var(--ink-3)}
.svcmenu-term[aria-expanded="true"]::before{content:"\2013"}

/* grid-template-rows 0fr -> 1fr animates to an auto height without JS
   measuring it. Engines too old for it just show the row open, which is
   the right way to fail: the text is readable either way. */
.svcmenu-def{
  display:grid; grid-template-rows:0fr;
  transition:grid-template-rows .35s var(--ease);
}
.svcmenu-term[aria-expanded="true"] + .svcmenu-def{grid-template-rows:1fr}
.svcmenu-def > p{
  overflow:hidden; margin:0;
  font-size:var(--s0); line-height:1.6; color:var(--muted);
}
.svcmenu-term[aria-expanded="true"] + .svcmenu-def > p{padding:0 .8rem 1rem 2.5rem}

.svcmenu-foot{
  padding:.9rem 1.2rem 1.1rem; border-top:1px solid var(--line-ui);
  display:grid; gap:.9rem;
}
.svcmenu-all{font-size:var(--s0); color:var(--bone);
  text-decoration:underline; text-underline-offset:3px}
.svcmenu-nav{display:none}

@media(max-width:1079px){
  /* below this width the panel is the only navigation on the blog and
     services pages, so it carries the page links too */
  .svcmenu-nav{
    display:flex; flex-wrap:wrap; gap:.75rem 1.4rem;
    padding-top:.9rem; border-top:1px solid var(--line); font-size:var(--s0);
  }
  .svcmenu-nav a{color:var(--muted); transition:color .35s var(--ease)}
  .svcmenu-nav a:hover{color:var(--bone)}
}

/* The Services button costs 105px of header, which pushed "Book consultation"
   past the right edge at 390px — measured, not guessed. Book comes out below
   640px because the always-visible .mobile-bar already carries it. */
@media(max-width:639px){
  .head-actions > .btn-solid{display:none}

  /* Same measured overflow as index.css, and worse here: on these pages the
     Services panel IS the navigation below 1080px, so clipping it costs more
     than a theme toggle. At 390px the brand's second line alone is about
     210px of a 350px content width — 30 characters at .63rem tracked to
     .16em. Dropping it is what makes the rest fit. The brand name above it
     still identifies the studio, and the same words sit in the footer. */
  .brand i{display:none}
}

@media(max-width:399px){
  /* 320px phones: brand + button + theme + burger is a tight fit, so the
     button loses a little padding rather than the burger losing its home */
  .svcmenu-btn{padding:.72rem .9rem}
}

@media(max-width:719px){
  /* full-width sheet with bigger type and taller rows, so it is genuinely
     readable on a phone rather than a shrunken desktop menu */
  .svcmenu{position:static}
  .svcmenu-panel{
    position:fixed; left:.6rem; right:.6rem; width:auto;
    top:calc(var(--nav-h) + .8rem);
    /* stop above the sticky .mobile-bar, or the last item hides behind it */
    max-height:calc(100dvh - var(--nav-h) - 6.2rem);
  }
  .svcmenu-term{padding:1rem .9rem; font-size:var(--s1)}
  .svcmenu-def > p{font-size:var(--s1); line-height:1.62}
  .svcmenu-term[aria-expanded="true"] + .svcmenu-def > p{padding:0 .9rem 1.1rem 2.7rem}
  .svcmenu-foot{padding:1rem 1.1rem 1.2rem}
}

/* Motion fallback. The panel must still open and close — only the
   animation goes. opacity/transform are reset so nothing is left faded. */
@media(prefers-reduced-motion: reduce){
  .svcmenu-panel{transition:none; opacity:1; transform:none}
  .svcmenu-def{transition:none}
  .svcmenu-btn svg{transition:none}
}


/* ============================================================
   SERVICE THUMBNAILS, THE DETAIL STRIP, AND THE REVEAL

   Fifteen pictures on a glossary page earn their place only if they help you
   find the word you came for. So they are small and beside the text, not
   full-width above it: the list stays scannable, the page stays short, and
   280px of source covers a 112px thumbnail two times over on a phone screen.
   ============================================================ */
/* Placement is explicit, not automatic. The item has three children — image,
   heading, paragraph — and auto-placement drops the paragraph into the first
   column of the next row, i.e. underneath the picture in a 72px-wide gutter.
   The picture spans both rows instead, and the text is pinned to column 2. */
.svc-item{
  display:grid;
  /* 132px max keeps 280px of source at 2.1x on a retina screen; below that
     the picture is too small to read at a glance, which is the only reason
     it is there */
  grid-template-columns:clamp(84px,16vw,132px) minmax(0,1fr);
  grid-template-rows:auto 1fr;
  column-gap:clamp(.9rem,2.5vw,1.3rem); row-gap:.55rem;
  align-items:start;
}
.svc-thumb   {grid-column:1; grid-row:1 / span 2}
.svc-item h2 {grid-column:2; grid-row:1}
.svc-item p  {grid-column:2; grid-row:2}

@media(max-width:599px){
  /* Beside a thumbnail the text column is only ~200px on a 390px phone, and
     133px on a 320px one — around 19 characters, which is not a reading
     measure. So on phones the picture keeps the heading company on the first
     row and the paragraph takes the full width underneath it. */
  .svc-item{grid-template-columns:clamp(72px,20vw,96px) minmax(0,1fr)}
  .svc-thumb{grid-row:1}
  .svc-item h2{align-self:center}
  .svc-item p{grid-column:1 / -1; grid-row:2}
}
.svc-thumb{
  width:100%; height:auto; aspect-ratio:1; object-fit:cover;
  border-radius:2px; background:var(--ink-2);
  /* the pictures are warm but brighter than the page; knocking them back a
     little stops fifteen of them shouting over the words they illustrate */
  filter:saturate(.92) brightness(.94);
  transition:filter .45s var(--ease);
}
.svc-item:hover .svc-thumb{filter:saturate(1) brightness(1)}
/* the heading and its generated number keep their own baseline; without this
   the number sits against the top of the picture rather than the text */
.svc-item h2{margin-top:-.15rem}

/* ---- the closing detail strip ---- */
/* The heading here was a tracked-out ALL-CAPS label ("UP CLOSE"). Dropped:
   it is one of the most recognisable generated-design tells, and a plain
   sentence heading in the site's own display face says more anyway. */
.detail-lede{margin:1rem 0 2rem}
.detail-grid{display:grid; gap:1px; background:var(--line)}
@media(min-width:700px){ .detail-grid{grid-template-columns:repeat(3,1fr)} }
.detail-grid figure{margin:0; background:var(--ink)}
.detail-grid img{
  width:100%; height:auto; aspect-ratio:5/4; object-fit:cover;
  filter:saturate(.92) brightness(.94);
}
.detail-grid figcaption{
  padding:1.1rem 1.2rem 1.5rem; font-size:var(--s0); color:var(--muted);
}
.disclosure{margin-top:1.8rem; max-width:56ch}

/* ============================================================
   SCROLL REVEAL

   index.html gets this from GSAP; this page has no GSAP and does not need
   400KB of it for one fade. IntersectionObserver does the same job in a few
   lines — see reveal.js.

   NOTE THE DIRECTION OF THE DEFAULT. Nothing is hidden until reveal.js adds
   .js-reveal to <html>, so no-JS and unsupported-browser both land on "fully
   visible" without anything having to go right. That is the opposite of the
   pattern on index.html, where elements start at opacity:0 and wait to be
   animated — which is exactly how four .privacy-item elements once ended up
   permanently invisible when their parent was missing from a hardcoded list.
   ============================================================ */
.js-reveal [data-reveal]{
  opacity:0; transform:translateY(14px);
  transition:opacity .6s var(--ease), transform .6s var(--ease);
  transition-delay:var(--reveal-delay, 0ms);
}
.js-reveal [data-reveal] .svc-thumb,
.js-reveal [data-reveal] img{
  transform:scale(1.04); transition:transform .8s var(--ease), filter .45s var(--ease);
  transition-delay:var(--reveal-delay, 0ms);
}
.js-reveal [data-reveal].is-in{opacity:1; transform:none}
.js-reveal [data-reveal].is-in .svc-thumb,
.js-reveal [data-reveal].is-in img{transform:none}

@media(prefers-reduced-motion:reduce){
  /* the content still arrives, it just arrives already there */
  .js-reveal [data-reveal],
  .js-reveal [data-reveal].is-in,
  .js-reveal [data-reveal] .svc-thumb,
  .js-reveal [data-reveal] img{
    opacity:1; transform:none; transition:none;
  }
}

/* ============================================================
   "UP CLOSE" — the three macros, and the odd-cell fix
   ============================================================ */
/* Held narrower than the page. This used to read "the source frames are 418px
   square" — they are not any more. They are 1040x832, so the old 1180px cap
   and the square override below were both workarounds for a limit that no
   longer exists. Widened to 1560px, which puts each of the three columns at
   about 520px: exactly 2x the source, so they are still shown at or under
   native and still read sharp, which was always the actual rule. */
.detail-wrap{max-width:1560px; margin-inline:auto}
/* The square override that stood here forced aspect-ratio:1 on frames that are
   now 5:4, so object-fit:cover was quietly cropping the sides off all three.
   Removing it lets the 5/4 above apply — and matches .r-macro on the home
   page, so the same three pictures are finally the same shape on both. */

/* Fifteen items in a two-column grid leaves one cell empty, and an empty cell
   shows the grid's own --line background — a grey block beside the last
   service that reads as a broken image. This fills it with the page colour.
   Only at the two-column breakpoint: in one column there is no orphan. */
@media(min-width:900px){
  .svc::after{content:""; background:var(--ink)}
}

/* ============================================================
   BHP ACADEMY — academy.html only

   A training page has a different job to the rest of the site: it is read by
   a barber deciding whether this is a serious place to learn, not by someone
   deciding whether to trust it with their head. So it gets one thing no other
   page has — --copper — and otherwise stays inside the same system.

   THE TWO LIST TREATMENTS ARE NOT INTERCHANGEABLE. .acad-modules is numbered
   because modules 01–08 are a genuine sequence; .acad-why is not, because
   five reasons are not five steps, and numbering things that have no order is
   the fastest way to make a page look machine-made.
   ============================================================ */

/* ---- hero ---- */
.acad-hero-grid{display:grid; gap:clamp(2rem,5vw,4rem); grid-template-columns:1fr; align-items:center}
@media(min-width:1000px){
  .acad-hero-grid{grid-template-columns:minmax(0,6fr) minmax(0,5fr)}
}
/* Sentence case, not tracked-out capitals. An all-caps eyebrow over every
   heading is the single most common generated-page tell, and this one is
   carrying a real sub-brand name rather than decorating the h1. */
.acad-eyebrow{margin:0 0 .9rem; font-size:var(--s-1); color:var(--copper)}
.acad-h1{max-width:16ch}
.acad-audience{margin-top:1.1rem; max-width:52ch; font-size:var(--s0); color:var(--muted)}
.acad-cta{display:flex; flex-wrap:wrap; gap:.8rem; margin-top:2rem}

.acad-figure{margin:0}
/* NO BORDER AND NO RADIUS, deliberately. The hero image is knocked out to
   transparency, so a frame would draw the exact rectangle the cut-out exists
   to avoid. If this image is ever swapped for a rectangular photograph, the
   border has to come back with it. */
.acad-figure img{width:100%; height:auto; display:block}
/* This was --s-1 — a footnote size for the one line explaining the biggest
   picture on the page, and unreadable at arm's length. --s2 and --bone make
   it a statement under the image rather than a photo credit. The max-width
   keeps it to about two lines so it never turns into a paragraph. */
.acad-figure figcaption{
  margin-top:1.2rem; max-width:30ch;
  font-size:var(--s2); line-height:1.35; color:var(--bone);
}

/* ---- what training covers ---- */
.acad-covers-sec{padding-block:clamp(2rem,5vh,3.5rem); border-block:1px solid var(--line)}
.acad-kicker{margin:0; font-family:var(--text); font-weight:400; letter-spacing:normal}
.acad-covers{
  list-style:none; margin:1.5rem 0 0; padding:0;
  display:grid; gap:clamp(1rem,2.5vw,2rem); grid-template-columns:1fr;
}
@media(min-width:640px){ .acad-covers{grid-template-columns:repeat(2,minmax(0,1fr))} }
@media(min-width:1080px){ .acad-covers{grid-template-columns:repeat(4,minmax(0,1fr))} }
.acad-covers li{display:flex; align-items:center; gap:.8rem; padding-top:1rem; border-top:1px solid var(--line)}
.acad-covers svg{width:22px; height:22px; flex:0 0 auto; color:var(--copper)}
/* Explicit metrics: the base h3 rule is display-serif and would otherwise
   impose its line-height and tracking on these labels. Same reason as
   .craft-item h3 and .step h3 on the home page. */
.acad-covers h3{
  margin:0; font-family:var(--text); font-weight:400; font-size:var(--s0);
  line-height:1.35; letter-spacing:normal; color:var(--bone);
}

/* ---- the eight modules ---- */
.acad-modules{
  list-style:none; margin:2.5rem 0 0; padding:0;
  border-top:1px solid var(--line);
}
@media(min-width:640px){
  .acad-modules{display:grid; grid-template-columns:repeat(2,minmax(0,1fr)); column-gap:clamp(1.5rem,4vw,3.5rem)}
}
/* TWO COLUMNS IS THE MAXIMUM, and it is a legibility decision rather than a
   layout one. Four across fitted once the descriptions were cut, but it
   forced the titles down to --s1 to stop them wrapping — and these eight
   lines are the only thing left carrying the curriculum, so they are the last
   place on the page that should be set small. Two columns buys the width to
   run them at --s2. */
.acad-modules li{
  display:grid; grid-template-columns:auto minmax(0,1fr); gap:clamp(.9rem,2.4vw,1.5rem);
  align-items:baseline;
  padding:clamp(1.15rem,2.5vw,1.6rem) 0; border-bottom:1px solid var(--line);
}
.acad-num{
  font-family:var(--display); font-size:var(--s3); line-height:1;
  color:var(--copper); font-variant-numeric:tabular-nums;
}
.acad-modules h3{
  margin:0; font-family:var(--text); font-weight:500; font-size:var(--s2);
  line-height:1.3; letter-spacing:normal; color:var(--bone);
}
.acad-modules p{margin:0; font-size:var(--s0); color:var(--muted); max-width:44ch}

/* ---- real client experience ---- */
.acad-real-grid{display:grid; gap:clamp(2rem,5vw,4.5rem); grid-template-columns:1fr; align-items:start}
@media(min-width:900px){
  .acad-real-grid{grid-template-columns:minmax(0,6fr) minmax(0,5fr)}
}
.acad-check{list-style:none; margin:0; padding:0; border-top:1px solid var(--line)}
.acad-check li{
  position:relative; padding:.75rem 0 .75rem 1.9rem;
  border-bottom:1px solid var(--line);
  font-size:var(--s0); color:var(--muted);
}
/* The tick is drawn, not a glyph — a ✓ character renders at a different
   weight in every fallback font and would drift away from the copper. */
.acad-check li::before{
  content:""; position:absolute; left:.15rem; top:1.15rem;
  width:9px; height:5px; transform:rotate(-45deg);
  border-left:1.5px solid var(--copper); border-bottom:1.5px solid var(--copper);
}
.acad-note{
  margin-top:clamp(2rem,4vw,3rem); padding-left:1.1rem;
  border-left:2px solid var(--copper);
  max-width:70ch; font-size:var(--s0); color:var(--muted);
}

/* ---- the journey ---- */
.acad-journey{
  list-style:none; margin:2.5rem 0 0; padding:0;
  display:grid; gap:0;
}
.acad-journey li{
  position:relative; padding:1.4rem 0 1.4rem 2rem;
  border-top:1px solid var(--line);
}
.acad-journey li::before{
  content:""; position:absolute; left:0; top:1.85rem;
  width:7px; height:7px; border-radius:50%; background:var(--copper);
}
/* the spine between the dots, dropped on the last one so the line stops */
.acad-journey li::after{
  content:""; position:absolute; left:3px; top:2.6rem; bottom:-.6rem;
  width:1px; background:var(--line);
}
.acad-journey li:last-child::after{display:none}
.acad-journey .acad-step{
  display:block; margin-bottom:.35rem;
  font-family:var(--display); font-size:var(--s2); line-height:1.15; color:var(--bone);
}
.acad-journey p{margin:0; font-size:var(--s0); color:var(--muted); max-width:46ch}
@media(min-width:900px){
  /* Five across. The dot now sits ON the continuous top rule, which is the
     timeline — so the vertical spine is not needed and is switched off. */
  .acad-journey{grid-template-columns:repeat(5,minmax(0,1fr)); gap:clamp(1rem,2.4vw,2.2rem)}
  .acad-journey li{padding:1.9rem 0 0}
  .acad-journey li::before{top:-4px}
  .acad-journey li::after{display:none}
}

/* ---- why learn here ---- */
.acad-why{list-style:none; margin:2.5rem 0 0; padding:0; border-top:1px solid var(--line)}
.acad-why li{padding:clamp(1.3rem,2.6vw,1.8rem) 0; border-bottom:1px solid var(--line)}
@media(min-width:900px){
  /* Term left, description right. Five items would leave an orphan in a
     two-column grid, so this splits inside each row instead of across. */
  .acad-why li{
    display:grid; grid-template-columns:minmax(0,4fr) minmax(0,7fr);
    gap:clamp(1.5rem,4vw,4rem); align-items:baseline;
  }
}
.acad-why h3{
  margin:0 0 .4rem; font-family:var(--text); font-weight:500; font-size:var(--s1);
  line-height:1.35; letter-spacing:normal; color:var(--bone);
}
@media(min-width:900px){ .acad-why h3{margin-bottom:0} }
.acad-why p{margin:0; font-size:var(--s0); color:var(--muted); max-width:56ch}

/* ---- register interest ---- */
.acad-register{border-top:1px solid var(--line)}
.acad-honest{
  margin-top:1.6rem; padding-left:1.1rem;
  border-left:2px solid var(--copper);
  max-width:66ch; font-size:var(--s0); color:var(--muted);
}
/* The exit back to the customer journey. It is the last thing on the page on
   purpose: somebody who read this far and realised they are a client, not a
   trainee, should not have to hunt for the way back. */
.acad-sep{margin-top:2.4rem; padding-top:1.4rem; border-top:1px solid var(--line)}

/* ---- the four-stage strip -----------------------------------------------
   Four photographs standing in for four paragraphs. They are the existing
   1000x1000 diagram frames, so nothing new had to be shot or generated, and
   they carry the alt text they already have on the home page. */
.acad-strip{
  list-style:none; margin:2rem 0 0; padding:0;
  display:grid; gap:clamp(.6rem,1.6vw,1rem);
  grid-template-columns:repeat(2,minmax(0,1fr));
}
@media(min-width:760px){ .acad-strip{grid-template-columns:repeat(4,minmax(0,1fr))} }
/* The frame exists so the wipe can be clipped to the photograph. Putting
   overflow:hidden on the <li> would clip the caption with it. */
.acad-frame{
  display:block; position:relative; overflow:hidden;
  border:1px solid var(--line); border-radius:3px;
}
.acad-strip img{width:100%; aspect-ratio:1/1; object-fit:cover; display:block}
.acad-cap{display:block; margin-top:.8rem; font-size:var(--s0); color:var(--muted)}

/* ---- the image wipe --------------------------------------------------
   DEFAULT IS UNCOVERED. The overlay is parked above the frame and clipped
   away, so with no JavaScript the four photographs are simply there. Only
   .js-reveal drops it over them, and only .is-in lifts it off — same
   direction-of-default as every other reveal on these pages.

   The cover step carries no transition on purpose: the transition lives on
   the .is-in rule, so the overlay snaps into place and then wipes away once.
   Put it on the .js-reveal rule instead and every image wipes down and back
   up on load, which reads as a fault. */
.acad-frame::after{
  content:""; position:absolute; inset:0; background:var(--ink);
  transform:translateY(-101%);
}
.js-reveal .acad-frame::after{transform:translateY(0)}
.js-reveal [data-reveal].is-in .acad-frame::after{
  transform:translateY(-101%);
  transition:transform 1s var(--ease);
  transition-delay:var(--reveal-delay,0ms);
}
/* A deeper, slower push than the global image reveal, so the four read as one
   movement across the row rather than four separate twitches. */
.js-reveal .acad-strip [data-reveal] img{transform:scale(1.14); transition-duration:1.25s}
.js-reveal .acad-strip [data-reveal].is-in img{transform:none}

@media(prefers-reduced-motion:reduce){
  .js-reveal .acad-frame::after,
  .js-reveal [data-reveal].is-in .acad-frame::after{transform:translateY(-101%); transition:none}
}

/* ---- the line that is deliberately not crossed --------------------------
   Set in the display face at --s2 because it is an argument, not a footnote:
   it is what makes eight bare titles read as confidence rather than as an
   unfinished page. */
.acad-withheld{
  margin-top:clamp(2rem,4vw,3rem); max-width:52ch;
  font-family:var(--display); font-size:var(--s2); line-height:1.4;
  color:var(--bone); letter-spacing:-.01em;
}

/* ---- the journey timeline draws itself ---------------------------------
   The one piece of non-entrance motion on the page. Each step's rule grows
   from the left as that step reveals, so the timeline is built by the scroll
   instead of already being there — which is the difference between motion
   that says something and another fade-and-rise.

   DEFAULT IS DRAWN, NOT UNDRAWN. scaleX(1) is the base state and only
   .js-reveal collapses it, so a failed script leaves five complete rules
   rather than five invisible ones. */
@media(min-width:900px){
  .acad-journey li{border-top:0}
  .acad-journey li::after{
    display:block; content:""; position:absolute;
    left:0; right:0; top:0; bottom:auto; width:auto; height:1px;
    background:var(--line); transform:scaleX(1); transform-origin:left;
    transition:transform .85s var(--ease); transition-delay:var(--reveal-delay,0ms);
  }
  .js-reveal .acad-journey li::after{transform:scaleX(0)}
  .js-reveal .acad-journey li.is-in::after{transform:scaleX(1)}
}
@media(prefers-reduced-motion:reduce){
  /* the timeline still exists, it just is not drawn for you */
  .js-reveal .acad-journey li::after,
  .js-reveal .acad-journey li.is-in::after{transform:scaleX(1); transition:none}
}

/* Two of the five step labels run to one line and three to two, which left
   the descriptions starting at five different heights. Reserving two lines on
   the label squares them up without shrinking the display type. */
@media(min-width:900px){
  .acad-journey .acad-step{min-height:2.3em}
}

/* ---- Academy reading size ---------------------------------------------
   Every secondary line on this page was set at --s0 (.95rem). That is the
   right size for a caption and the wrong size for the only sentence a reader
   gets on a subject, and once the module descriptions were cut these short
   lines became the whole argument — so they are raised one step to --s1.

   Kept as one block rather than edited into each rule above so the decision
   is visible and reversible in a single place. The max-widths are in ch, so
   line length holds as the size goes up. */
.acad-audience,
.acad-check li,
.acad-note,
.acad-journey p,
.acad-why p,
.acad-honest{font-size:var(--s1)}

/* The four labels under "Training covers" and the five reasons are headings,
   not body copy, so they take the heading step rather than the body one. */
.acad-covers h3{font-size:var(--s1)}
.acad-why h3{font-size:var(--s2); line-height:1.3}

/* ============================================================
   SCRIPT COVERAGE — Devanagari and Gujarati

   NOT A THIRD AND FOURTH TYPEFACE, and rule 6 is not being broken. Bodoni
   Moda and Archivo carry no Devanagari and no Gujarati at all, so
   "English, हिन्दी, ગુજરાતી" was already rendering in three different faces:
   Archivo for the Latin, then whatever the OS happened to offer for each of
   the other two — Nirmala UI and Shruti on Windows, something else on a Mac,
   something else again on Android. Three unplanned faces inside one h3.

   Naming the stacks makes the fallback a decision instead of an accident. It
   is the same fix already applied to the Hindi review card, generalised: this
   heading is not the only place it happens, the footer on every page carries
   the same three scripts.

   letter-spacing is reset because the display face runs -.015em tracking, and
   negative tracking closes up Devanagari and Gujarati conjuncts until they
   misread. These selectors target the span directly, so they beat the
   inherited font-family from any parent heading rule. */
/* Marathi shares the script, so it shares the stack. Grouped rather than
   duplicated: without a :lang(mr) rule at all, मराठी falls through to the
   display serif, which carries no Devanagari — so it renders in whatever the
   system substitutes and sits visibly wrong beside हिन्दी on the same line. */
:lang(hi), :lang(mr){
  font-family:"Noto Sans Devanagari","Nirmala UI","Kohinoor Devanagari",
              "Devanagari Sangam MN",sans-serif;
  letter-spacing:normal;
}
:lang(gu){
  font-family:"Noto Sans Gujarati","Shruti","Gujarati Sangam MN",
              "Nirmala UI",sans-serif;
  letter-spacing:normal;
}

/* ---- the classroom photograph -----------------------------------------
   FULL-BLEED ON A PHONE, CONTAINED ABOVE IT. This is a wide 3:2 room with
   five people in it, and inside .wrap's padding it renders about 350px on a
   390px screen — small enough that the bench and the blocks stop reading.
   Going edge to edge buys back the padding and, more usefully, makes the
   narrow crop look like a decision rather than a squeeze.

   Above 720px it comes back inside the page margin and takes a cap, because
   full-bleed on a wide desktop would put a 1400px photograph next to type
   held at 62ch and overwhelm the section it belongs to.

   The caption keeps .wrap so it stays aligned with the body copy even while
   the picture bleeds past it. */
/* overflow:hidden is load-bearing, not tidying. .js-reveal [data-reveal] img
   starts at scale(1.04) and animates to none, and below 720px these two
   figures are full-bleed — so a 390px image is painted 406px wide, hanging
   8px off each edge and giving the whole PAGE a horizontal scrollbar.
   Measured on a real 390px viewport: documentElement.scrollWidth was 398
   against a clientWidth of 390.
   .acad-room also carries data-reveal-repeat, so its scale is re-applied
   every time the figure re-enters the viewport — the overflow came back on
   every pass, which is the sort of thing that reads as a wobbly page rather
   than as one broken rule. Clipping keeps the zoom and drops the overflow. */
.acad-room,.acad-band{margin:clamp(2.2rem,5vw,3.5rem) 0 0; overflow:hidden}
.acad-room img,.acad-band img{width:100%; height:auto; display:block}
.acad-room figcaption,.acad-band figcaption{
  margin-top:.9rem; font-size:var(--s0); color:var(--muted);
}
@media(min-width:720px){
  .acad-room,.acad-band{
    max-width:var(--max); margin-inline:auto;
    padding-inline:var(--pad);
  }
  .acad-room img,.acad-band img{border:1px solid var(--line); border-radius:3px}
  .acad-room figcaption,.acad-band figcaption{padding-inline:0}
}

/* ---- the 1080-1199 squeeze --------------------------------------------
   The nav links switch on at 1080px, and at exactly that width the header
   has to hold: brand + six links + Services + theme + Book + burger. It did
   not. The brand broke onto three lines and "Book consultation" wrapped into
   the burger — measured in a real 1080px viewport, not guessed.

   The brand's second line goes, for the same reason it goes on a phone: at
   .63rem tracked to .16em it is the widest single element in the header and
   the least load-bearing. Below 1200px the brand name alone identifies the
   studio, and the same words sit in the footer.

   nowrap on the links and the button because a two-line nav item is worse
   than a slightly tighter row — it changes the header's height and pushes
   everything under it. */
@media(max-width:1199px){
  .brand i{display:none}
}
.nav-links a,
.site-nav a,
.nav .btn,
.head-actions .btn{white-space:nowrap}

/* ---- anchors must clear the fixed header --------------------
   index.css has carried this rule for a long time; style.css never did, so
   every fragment link into these pages landed underneath the header.

   Measured on the live site: clicking "Front hairline system" on the home
   page lands on /services#front-hairline, and the heading it was named
   after sat at top:32px with the fixed header occupying 0-83px. The
   visitor arrives at the right section and cannot see its title.

   The targets are not all sections — services.html hangs its six ids on
   <li class="svc-item">, so section[id] alone would miss every one of
   them. :target catches whatever the fragment actually points at; the
   element selectors are there for engines that resolve the scroll before
   applying :target.

   A flat value rather than index.css's clamp, because this header is a
   measured 83-84px at 360, 390, 768, 1024 and 1440 — it does not scale, so
   a fluid margin would only be guessing. 6.5rem leaves about 20px of air
   under the header. */
:target,
section[id],
main[id],
li[id]{scroll-margin-top:6.5rem}

/* ---- the rest of the guidelines sweep, which index.css already had ------
   These pages share a header, a footer and a type scale with index.html but
   not a stylesheet, so three fixes made there never reached here.

   touch-action:manipulation drops the ~300ms a mobile browser waits to see
   whether a tap is the first half of a double-tap-to-zoom. Scoped to tap
   targets; nothing on these pages owns a custom gesture the way the home
   page's before/after slider does.

   text-wrap:balance evens the rag on multi-line headings. h2 and h3 only —
   h1 on these pages is a single editorial line and balancing it would fight
   a break that was set deliberately. */
a, button, summary, [role="button"]{touch-action:manipulation}
h2, h3{text-wrap:balance}
