/* ============================================================================
   SEVENTH HOUR — brand layer
   Palette, grain, atmosphere, curved section flow, plinths and motion.
   Page structure is authored in Elementor; this file supplies the brand.
   Everything keys off a handful of `sh-` classes applied to Elementor
   containers, so a section can be restyled by changing one class in the editor.
   ========================================================================= */

:root{
  /* ---- Three colours only. Everything else is a neutral step between. ---- */
  --sh-black:#050505;
  --sh-grey:#353535;
  --sh-ivory:#F3F5F3;

  --sh-void:#050505;
  --sh-near-black:#0a0a0a;
  --sh-base:#0d0d0d;
  --sh-graphite:#141414;
  --sh-graphite-2:#1b1b1b;
  --sh-charcoal:#242424;
  --sh-charcoal-2:#353535;

  --sh-text:#F3F5F3;
  --sh-text-2:#9b9b99;
  /* Lifted from the system's #6b6b6a, which failed WCAG AA on every dark surface
     it is used on (2.91:1 to 3.64:1 against the 4.5:1 required at 13px). This
     token carries every eyebrow, caption and fine-print line on the site, so the
     failure was site-wide. #8a8a88 measures 4.49:1 to 5.62:1 on the same four
     surfaces and is still clearly a tertiary tone. */
  --sh-text-3:#8a8a88;
  --sh-on-accent:#050505;

  --sh-hairline:rgba(243,245,243,.08);
  --sh-hairline-strong:rgba(243,245,243,.16);
  --sh-hairline-accent:rgba(243,245,243,.32);

  /* ---- Shape. Broad rounded shells that layer into one another. ---- */
  --sh-radius-section:clamp(26px, 3.6vw, 56px);
  --sh-radius-2xl:clamp(20px, 2.4vw, 36px);
  --sh-radius-lg:20px;
  --sh-radius-md:14px;
  --sh-radius-pill:999px;

  /* ---- Rhythm. 8px-rooted; sections breathe wider as the viewport grows. ---- */
  --sh-pad-y:clamp(72px, 8.4vw, 128px);
  --sh-pad-x:clamp(20px, 3.4vw, 48px);
  --sh-page-inset:clamp(6px, .8vw, 10px);
  --sh-overlap:clamp(22px, 2.8vw, 40px);
  --sh-container:1200px;

  /* ---- Motion. Minimal and functional. ---- */
  --sh-ease:cubic-bezier(.16,1,.3,1);
  --sh-dur:900ms;
  --sh-press:.96;

  --sh-shadow-card:0 1px 0 rgba(243,245,243,.04) inset, 0 18px 44px rgba(0,0,0,.5);
  --sh-shadow-plinth:0 1px 0 rgba(243,245,243,.05) inset, 0 34px 70px -18px rgba(0,0,0,.72);
}

/* Inverted composition — the print voice, usable on any single section. */
.sh-invert{
  --sh-void:#F3F5F3;
  --sh-near-black:#eceeec;
  --sh-base:#F3F5F3;
  --sh-graphite:#e8eae8;
  --sh-graphite-2:#e1e3e1;
  --sh-charcoal:#d6d8d6;
  --sh-charcoal-2:#c7c9c7;
  --sh-text:#050505;
  --sh-text-2:#4a4a4a;
  --sh-text-3:#7a7a79;
  --sh-on-accent:#F3F5F3;
  --sh-hairline:rgba(5,5,5,.10);
  --sh-hairline-strong:rgba(5,5,5,.20);
  --sh-hairline-accent:rgba(5,5,5,.42);
}

/* ---------------------------------------------------------------- ground -- */

html{ background:var(--sh-black); }
body{
  background:var(--sh-black);
  color:var(--sh-text);
  font-family:Inter, system-ui, -apple-system, sans-serif;
  font-size:18px; line-height:1.5; letter-spacing:-0.05em;
  -webkit-font-smoothing:antialiased;
  /* `clip`, NOT `hidden`. `overflow-x:hidden` makes body a scroll container, which
     silently breaks position:sticky for everything inside it — including the
     stacking panels below. `clip` contains without creating that container.
     (Horizontal overflow is 0 on every page anyway; this is belt and braces.) */
  overflow-x:clip;
}
body, .site-main{ margin:0; }

/* Hierarchy comes from scale, tracking and space — never from weight.
   600 is the ceiling; 700 is not in this system. */
h1,h2,h3,h4,h5,h6{ font-weight:400; letter-spacing:-0.05em; line-height:1.12; }
strong,b{ font-weight:500; }

/* The single serif interruption: one word or one number per view. */
.sh-serif{ font-family:'Times New Roman', Times, serif; font-style:italic; font-weight:400; letter-spacing:-0.05em; }

/* The system's main labelling device. The one positive-tracking exception. */
.sh-eyebrow, .sh-eyebrow *{
  font-size:13px; font-weight:500; line-height:1.3;
  letter-spacing:.12em; text-transform:uppercase; color:var(--sh-text-3);
}

/* Sequence marker — grey, never white. */
.sh-index, .sh-index *{
  font-weight:400; line-height:.92; letter-spacing:-0.05em; color:var(--sh-text-3);
}

/* --------------------------------------------------------------- grain ---- */
/* Two noise frequencies layered for a scanned-film feel. Obvious at a glance:
   if a dark surface looks clean, the tier is too low. */

.sh-grain-light, .sh-grain, .sh-grain-heavy{ position:relative; isolation:isolate; }
.sh-grain-light > *, .sh-grain > *, .sh-grain-heavy > *{ position:relative; z-index:1; }
.sh-grain-light::after, .sh-grain::after, .sh-grain-heavy::after{
  content:""; position:absolute; inset:0; pointer-events:none; z-index:0;
  border-radius:inherit;
  background-image:
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='180' height='180'%3E%3Cfilter id='a'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23a)'/%3E%3C/svg%3E"),
    url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='300' height='300'%3E%3Cfilter id='b'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.32' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23b)'/%3E%3C/svg%3E");
  background-size:180px 180px, 300px 300px;
}
.sh-grain-light::after{ opacity:.34; mix-blend-mode:soft-light; }   /* cards, forms */
.sh-grain::after{ opacity:.64; mix-blend-mode:soft-light; }          /* standard sections */
.sh-grain-heavy::after{ opacity:.95; mix-blend-mode:overlay; }       /* heroes, big imagery */
.sh-grain-heavy::before{
  content:""; position:absolute; inset:0; pointer-events:none; z-index:0;
  border-radius:inherit;
  opacity:.16; mix-blend-mode:normal;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='90' height='90'%3E%3Cfilter id='c'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.4' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23c)'/%3E%3C/svg%3E");
  background-size:90px 90px;
}
/* The same lift, weaker, on the two lower tiers. */
.sh-grain::before, .sh-grain-light::before{
  content:""; position:absolute; inset:0; pointer-events:none; z-index:0;
  border-radius:inherit;
  background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='120' height='120'%3E%3Cfilter id='d'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='1.1' numOctaves='2' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23d)'/%3E%3C/svg%3E");
  background-size:120px 120px; mix-blend-mode:normal;
}
.sh-grain::before{ opacity:.10; }
.sh-grain-light::before{ opacity:.055; }
/* On ivory the grain multiplies at roughly half strength so paper stays tactile. */
.sh-invert .sh-grain-light::after, .sh-invert.sh-grain-light::after{ opacity:.30; mix-blend-mode:multiply; }
.sh-invert .sh-grain::after, .sh-invert.sh-grain::after{ opacity:.38; mix-blend-mode:multiply; }
.sh-invert .sh-grain-heavy::after, .sh-invert.sh-grain-heavy::after{ opacity:.46; mix-blend-mode:multiply; }
.sh-invert .sh-grain-heavy::before, .sh-invert.sh-grain-heavy::before{ opacity:.13; mix-blend-mode:multiply; }
.sh-invert .sh-grain::before, .sh-invert.sh-grain::before,
.sh-invert .sh-grain-light::before, .sh-invert.sh-grain-light::before{ opacity:.07; mix-blend-mode:multiply; }

/* ---------------------------------------------------------- atmosphere ---- */
/* Every screen is lit from one side rather than flat black. */
.sh-atmosphere{
  background-color:#050505;
  background-image:radial-gradient(140% 105% at 26% -16%, #3d3d3d 0%, #272727 32%, #151515 62%, #080808 100%);
}
.sh-invert.sh-atmosphere{
  background-color:#F3F5F3;
  background-image:radial-gradient(140% 105% at 26% -16%, #ffffff 0%, #F3F5F3 42%, #e4e6e4 82%);
}

/* ------------------------------------------------------- section flow ----- */
/* Broad rounded stages, each overlapping the one above on a rising z-index,
   so the page flows instead of stacking as rectangles. */

/* Only the things Elementor cannot express live here. Padding, margin (including
   the negative overlap) and corner radius are set in each stage's own Elementor
   settings instead, so they stay editable in the UI at every breakpoint. */
.sh-stage{
  position:relative;
  overflow:hidden;
  /*
   * Two devices make the seam read as a deliberate transition rather than a join.
   *
   * The inset hairline is the design system's own `--inset-hairline-top`: a single
   * ivory light seam across the top edge. Because it is an INSET shadow it follows
   * the border-radius, so it draws the curve instead of leaving it as a dark-on-dark
   * corner you can only infer. This is what the rounded shells were missing.
   *
   * The upward shadow is cast onto the stage above, so each panel visibly sits OVER
   * its neighbour. Without it the panels are coplanar and the overlap has nothing to
   * read against — which is why the stack looked flat and the curves looked odd.
   */
  box-shadow:
    inset 0 1px 0 rgba(243,245,243,.10),
    0 -26px 52px -26px rgba(0,0,0,.62);
}
/* On the ivory band the seam and the lift invert, or they vanish. */
.sh-invert.sh-stage{
  box-shadow:
    inset 0 1px 0 rgba(255,255,255,.55),
    0 -26px 52px -26px rgba(0,0,0,.28);
}
/* The first stage has nothing above it to cast onto, and it now runs full-bleed
   under the header — whose own bottom border already draws that line. A seam here
   would just double it. */
.sh-stage:first-of-type{ box-shadow:none; }
/* A stage is inset from the viewport edge by a left/right margin. Elementor also
   gives every full-width container `width: 100%`, and 100% PLUS those margins
   overflows the page horizontally. `auto` makes it fill what is left after the
   margins instead. Specificity has to beat Elementor's own `.e-con` rule. */
.sh-stage.e-con{ width:auto; max-width:none; }

/* Rising z-index so each stage sits over the one above it. */
.sh-z1{ z-index:1; } .sh-z2{ z-index:2; } .sh-z3{ z-index:3; }
.sh-z4{ z-index:4; } .sh-z5{ z-index:5; } .sh-z6{ z-index:6; } .sh-z7{ z-index:7; }

/* Tonal ladder — adjacent stages sit one step apart. */
/* Flat stage colours are set as ordinary Elementor backgrounds. Only the
   atmosphere sweep, which Elementor has no control for, is applied by class. */

/* The last stage runs into the footer rather than curving away from it. */
.sh-stage--closing{ border-bottom-left-radius:0; border-bottom-right-radius:0; }

.sh-container{ max-width:var(--sh-container); margin-inline:auto; width:100%; }
.sh-measure{ max-width:640px; margin-inline:auto; }

/* ------------------------------------------------------------- cards ------ */

.sh-card{
  background:var(--sh-graphite);
  border:1px solid var(--sh-hairline);
  border-radius:var(--sh-radius-2xl);
  overflow:hidden;
  transition:border-color var(--sh-dur) var(--sh-ease), transform var(--sh-dur) var(--sh-ease);
}
.sh-card:hover{ border-color:var(--sh-hairline-strong); }

.sh-card-tile{
  background:var(--sh-graphite-2);
  border:1px solid var(--sh-hairline);
  border-radius:var(--sh-radius-2xl);
  overflow:hidden;
}

/* Media panels — framed, grained image plinths. Not glassmorphism.
   These are Image WIDGETS, not container backgrounds: a background image shows
   in the Structure panel as an empty Container and is effectively unfindable. */
.sh-media{
  position:relative; isolation:isolate; overflow:hidden;
  border-radius:var(--sh-radius-2xl);
  background:#141414;                 /* seen only while the photo decodes */
}
.sh-media img{
  display:block; width:100%; height:100%; object-fit:cover;
  border-radius:inherit;
}
/* The corner falloff used to live on ::after — the same pseudo-element the grain
   needs, so the two silently overwrote each other and media wells lost their
   grain. An inset shadow gives the falloff without consuming a pseudo-element. */
.sh-media{
  box-shadow:inset 0 0 90px 24px rgba(5,5,5,.34),
             inset 0 0 0 1px var(--sh-hairline);
}
/* Grain must sit ABOVE the photograph, not behind it. */
.sh-media.sh-grain-heavy::after, .sh-media.sh-grain-heavy::before,
.sh-media.sh-grain::after, .sh-media.sh-grain::before,
.sh-media.sh-grain-light::after, .sh-media.sh-grain-light::before{ z-index:2; }
.sh-media--square{ border-radius:0; }

/* Fixed-ratio image wells so a missing photo never collapses the layout.
   The ratio is on the element itself, so these work as Elementor containers
   with a background image — which is how a photo gets swapped in one click. */
.sh-ratio-wide{ aspect-ratio:25/12; }
.sh-ratio-work{ aspect-ratio:4/3; }
.sh-ratio-tall{ aspect-ratio:3/4; }
.sh-ratio-wide > img, .sh-ratio-work > img, .sh-ratio-tall > img{ width:100%; height:100%; object-fit:cover; }
@media (max-width:767px){
  .sh-ratio-wide{ aspect-ratio:16/10; }
}

/* Placeholder wells, used until real Seventh Hour photography lands. */
.sh-photo-pending{
  background:linear-gradient(160deg,#242424 0%,#161616 55%,#0d0d0d 100%);
}
/* An Image widget with no image selected still has to hold its slot open. */
.sh-media.sh-photo-pending img{ display:none; }

/* ------------------------------------------------------------ plinths ----- */
/* The pricing device: graphite, hairline, low soft lift, one ivory light seam. */

.sh-plinth{
  position:relative; overflow:hidden;
  background:linear-gradient(180deg,#1b1b1b 0%,#141414 60%,#101010 100%);
  border:1px solid var(--sh-hairline);
  border-radius:var(--sh-radius-lg);
  box-shadow:var(--sh-shadow-card);
}
.sh-plinth--featured{
  background:linear-gradient(180deg,#353535 0%,#242424 58%,#1b1b1b 100%);
  border-color:var(--sh-hairline-strong);
  box-shadow:var(--sh-shadow-plinth);
}
/* One step lighter, one step taller, lifted — no glow, no coloured edge. */
.sh-plinth--featured::before{
  content:""; position:absolute; top:0; left:16%; right:16%; height:1px; z-index:3;
  background:linear-gradient(90deg,transparent,rgba(243,245,243,.30),transparent);
}
@media (min-width:1025px){
  .sh-plinth--featured{ margin-top:-28px; }
}

.sh-rule{ border-top:1px solid var(--sh-hairline); }
.sh-rule-dotted{ border-top:1px dotted var(--sh-hairline-strong); }

/* -------------------------------------------------------------- buttons --- */
/* Full capsule is the one non-negotiable control shape. */

.elementor-button,
.sh-btn .elementor-button{
  border-radius:var(--sh-radius-pill) !important;
  font-weight:500; font-size:15px; letter-spacing:-0.05em; line-height:1;
  position:relative; overflow:hidden;
  transition:transform 150ms var(--sh-ease), background-color 250ms var(--sh-ease),
             color 250ms var(--sh-ease), border-color 250ms var(--sh-ease);
}
.elementor-button:active{ transform:scale(var(--sh-press)); }

/* One hairline light sweep across the base of a primary button on hover. */
.sh-btn--primary .elementor-button::after{
  content:""; position:absolute; left:12%; right:12%; bottom:0; height:1px;
  background:linear-gradient(90deg,transparent,rgba(5,5,5,.45),transparent);
  opacity:0; transform:translateY(2px);
  transition:opacity 250ms var(--sh-ease), transform 250ms var(--sh-ease);
}
.sh-btn--primary .elementor-button:hover::after{ opacity:1; transform:none; }
.sh-btn--secondary .elementor-button:hover{ border-color:var(--sh-hairline-accent) !important; }

/* ------------------------------------------------------------- filters ---- */
/* Portfolio category pills. */
.sh-filters{ display:flex; flex-wrap:wrap; gap:8px; }
.sh-filter{
  appearance:none; cursor:pointer;
  font:500 13px/1 Inter, system-ui, sans-serif; letter-spacing:-0.05em;
  padding:10px 18px; border-radius:var(--sh-radius-pill);
  background:transparent; color:var(--sh-text-2);
  border:1px solid var(--sh-hairline);
  transition:color 250ms var(--sh-ease), border-color 250ms var(--sh-ease), background-color 250ms var(--sh-ease);
}
.sh-filter:hover{ color:var(--sh-text); border-color:var(--sh-hairline-strong); }
.sh-filter[aria-pressed="true"]{ background:var(--sh-ivory); color:var(--sh-black); border-color:var(--sh-ivory); }
.sh-hidden-tile{ display:none !important; }

/* --------------------------------------------------------------- motion --- */
/*
 * The hidden state is applied ONLY to elements the script has judged to start
 * below the fold — it marks those `sh-armed` and nothing else. Anything in the
 * first screenful is therefore never hidden, not even for a frame, and cannot
 * end up stuck invisible if the observer misfires or the script fails.
 *
 * Keying the hidden state off `.sh-reveal` plus a global html class (the previous
 * approach) meant every reveal on the page, including the hero, depended on
 * JavaScript to become visible. That is the wrong risk to take for a headline.
 */
.sh-armed{ opacity:0; transform:translateY(22px); will-change:opacity, transform; }
.sh-armed.sh-in{
  opacity:1; transform:none;
  transition:opacity var(--sh-dur) var(--sh-ease), transform var(--sh-dur) var(--sh-ease);
}
.sh-armed.sh-d1.sh-in{ transition-delay:90ms; }
.sh-armed.sh-d2.sh-in{ transition-delay:180ms; }
.sh-armed.sh-d3.sh-in{ transition-delay:270ms; }
.sh-armed.sh-d4.sh-in{ transition-delay:360ms; }
.sh-armed.sh-d5.sh-in{ transition-delay:450ms; }
.sh-armed.sh-d6.sh-in{ transition-delay:540ms; }

/* A slower, larger settle for a full-bleed image coming into frame. */
.sh-armed.sh-reveal-media{ opacity:0; transform:scale(1.02); }
.sh-armed.sh-reveal-media.sh-in{
  opacity:1; transform:none;
  transition:opacity 1100ms var(--sh-ease), transform 1400ms var(--sh-ease);
}

@media (prefers-reduced-motion:reduce){
  .sh-armed, .sh-armed.sh-reveal-media{ opacity:1 !important; transform:none !important; transition:none !important; }
  html{ scroll-behavior:auto; }
}
html{ scroll-behavior:smooth; }

/* ---------------------------------------------------------------- logo ---- */
/* Master artwork. Size it by WIDTH, never height. Never redraw in live type. */
.sh-logo img{ width:100%; height:auto; display:block; }

/* --------------------------------------------------------------- header --- */

.sh-header{
  position:sticky; top:0; z-index:50;
  background-color:rgba(5,5,5,.72);
  -webkit-backdrop-filter:saturate(140%) blur(14px);
  backdrop-filter:saturate(140%) blur(14px);
  border-bottom:1px solid var(--sh-hairline);
}
/*
 * REMOVED: a generic `.sh-nav a { font-size:13px; padding-bottom:4px }` block.
 *
 * It was a leftover from the first pass, superseded by `.sh-nav__list a` but
 * never deleted — and because `.sh-nav a` is (0,1,1) against `.sh-header__cta`'s
 * (0,1,0), it silently won on the CTA. That is what kept the button's label at
 * 13px, and its stray bottom padding is what pushed the label off the capsule's
 * centre. The nav links themselves were unaffected, since `.sh-nav__list a`
 * out-ranks it — which is exactly why the button looked wrong while the links
 * looked fine.
 *
 * Do not reintroduce element-qualified selectors under `.sh-nav`: the CTA is an
 * `<a>` inside it and will inherit them.
 */

/* --------------------------------------------------------------- footer --- */
.sh-footer{ background:var(--sh-void); border-top:1px solid var(--sh-hairline); }
.sh-footer a{ color:var(--sh-text-2); text-decoration:none; border:0; transition:color 250ms var(--sh-ease); }
.sh-footer a:hover{ color:var(--sh-text); }

/* ------------------------------------------------------------ responsive -- */

/* Elementor's own grids collapse at its breakpoints; these rules cover the
   brand-layer pieces that Elementor does not know about. */
@media (max-width:1024px){
  .sh-plinth--featured{ margin-top:0; }
}
@media (max-width:767px){
  body{ font-size:16px; }
  /* No border-radius here. Corner radii are per-stage Elementor values (top
     corners only, square bottoms) and a blanket rule re-rounded all four on
     mobile, putting the notches straight back at small widths. */
  .sh-eyebrow, .sh-eyebrow *{ font-size:12px; }
  /* Buttons go full-width on a phone so a thumb cannot miss them. */
  .sh-btn-stack .elementor-widget-button{ width:100%; }
  .sh-btn-stack .elementor-button{ width:100%; justify-content:center; }
}

/* Links inside body copy keep the system's hairline underline. */
.sh-stage p a{ color:var(--sh-text); border-bottom:1px solid var(--sh-hairline-strong); text-decoration:none; }
.sh-stage p a:hover{ border-bottom-color:var(--sh-text); }

/* Elementor sets its own container background painting order; make sure a
   stage's own background wins over the section wrapper's default. */
.sh-stage.e-con{ --e-con-grid-template-columns:none; }

/* --------------------------------------------------- switches & chips ----- */
/* Category filter (Work) and property-size switch (Pricing) share one look.
   Each chip is a real Elementor button, so its label is edited in the editor. */
.sh-switch{ display:flex; flex-wrap:wrap; gap:8px; }
.sh-filter .elementor-button{
  background:transparent !important;
  color:var(--sh-text-2) !important;
  border:1px solid var(--sh-hairline) !important;
  border-radius:var(--sh-radius-pill) !important;
  padding:11px 20px !important;
  font-size:13px !important; font-weight:500 !important; letter-spacing:-0.05em;
}
.sh-filter .elementor-button:hover{
  color:var(--sh-text) !important; border-color:var(--sh-hairline-strong) !important;
}
.sh-filter.sh-on .elementor-button{
  background:var(--sh-ivory) !important;
  color:var(--sh-black) !important;
  border-color:var(--sh-ivory) !important;
}
/* Chips never carry the primary button's light sweep. */
.sh-filter .elementor-button::after{ content:none !important; }

/* Filtered items. Hidden by class so the editor still shows every one of them. */
.sh-hidden-tile{ display:none !important; }
.elementor-editor-active .sh-hidden-tile{ display:flex !important; opacity:.45; }

/* Price rows for the property-size switch. */
.sh-price.sh-hidden-tile{ display:none !important; }

/* ================================================= header &amp; footer ======= */
/* Interim chrome. An Elementor Pro Theme Builder header/footer supersedes these
   templates automatically, and these rules simply stop applying. */

.sh-header__inner{
  max-width:calc(var(--sh-container) + 96px);
  margin-inline:auto;
  padding:18px clamp(16px, 3vw, 40px);
  display:flex; align-items:center; justify-content:space-between; gap:24px;
}
.sh-header__logo{ display:block; width:150px; flex:0 0 auto; border:0; }
.sh-header__logo img{ width:100%; height:auto; display:block; }

.sh-nav{ display:flex; align-items:center; gap:clamp(20px, 2.4vw, 36px); }
.sh-nav__list{ display:flex; align-items:center; gap:clamp(20px, 2.4vw, 36px); list-style:none; margin:0; padding:0; }
/*
 * 13px read too small next to the CTA. 14.5px is still restrained but holds its
 * own. The links carry NO bottom padding: it pushed their text up off the CTA's
 * centre line, which is why the button looked vertically misaligned. The active
 * underline is a detached pseudo-element instead, so it marks the current page
 * without taking part in layout.
 */
.sh-nav__list a{
  font-size:14.5px; font-weight:400; letter-spacing:-0.04em; color:var(--sh-text-2);
  text-decoration:none; border:0; position:relative; display:inline-flex;
  align-items:center; line-height:1;
  /* Same box height as the CTA capsule, so both centre on exactly the same line
     instead of within 2-3px of each other. It also gives the links a proper
     44px target rather than a 15px sliver of text. */
  min-height:44px;
  transition:color 250ms var(--sh-ease);
}
.sh-nav__list a:hover{ color:var(--sh-text); }
.sh-nav__list .current-menu-item > a,
.sh-nav__list .current_page_item > a{ color:var(--sh-text); }
.sh-nav__list .current-menu-item > a::after,
.sh-nav__list .current_page_item > a::after{
  content:""; position:absolute; left:0; right:0; bottom:11px; height:1px;
  background:var(--sh-ivory);
}

/*
 * Sized by height rather than by vertical padding, with the label centred by the
 * flexbox itself — so the text cannot drift off centre as the font renders, which
 * is what made it look off before. 44px is also the minimum comfortable tap
 * target. `line-height:1` keeps the capsule's proportions honest: a 999px radius
 * on a box whose height came from padding plus leading looked oversized for the
 * label inside it.
 */
.sh-nav .sh-header__cta,
.sh-header__cta{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:44px; padding:0 26px;
  border-radius:var(--sh-radius-pill);
  background:var(--sh-ivory); color:var(--sh-black) !important;
  font-size:14.5px; font-weight:500; letter-spacing:-0.04em; line-height:1;
  text-decoration:none; border:0; white-space:nowrap;
  transition:background-color 250ms var(--sh-ease), transform 150ms var(--sh-ease);
}
.sh-header__cta:hover{ background:#fff; }
.sh-header__cta:active{ transform:scale(var(--sh-press)); }

/* The elongated bar in the wordmark is the brand's own motion device; the mobile
   toggle borrows it rather than introducing a hamburger from another system. */
/*
 * The menu button.
 *
 * Scoped under .sh-header on purpose. The Elementor global kit styles EVERY bare
 * <button> on the page — `.elementor-kit-5 button { background:ivory;
 * border-radius:999px; padding:16px 28px }` — at specificity (0,1,1), which beats
 * a bare `.sh-header__toggle` (0,1,0). The burger came out as an ivory capsule
 * with ivory bars inside it: a white dot that did nothing visible when tapped.
 *
 * `.sh-header .sh-header__toggle` is (0,2,0) and wins. Every property the kit
 * sets is reset explicitly rather than left to chance, because the kit's button
 * styling is editable in Elementor and will change again.
 */
.sh-header .sh-header__toggle{
  display:none; position:relative;
  width:44px; height:44px; min-height:44px; flex:0 0 auto;
  background:none; background-color:transparent;
  border:0; border-radius:0; box-shadow:none;
  cursor:pointer; padding:0; margin:0;
  -webkit-appearance:none; appearance:none;
  color:var(--sh-text);
}
.sh-header .sh-header__toggle:hover,
.sh-header .sh-header__toggle:focus{ background:none; background-color:transparent; }
.sh-header .sh-header__toggle:focus-visible{
  outline:2px solid var(--sh-text); outline-offset:2px; border-radius:6px;
}
.sh-header__toggle-bar,
.sh-header__toggle-bar::before,
.sh-header__toggle-bar::after{
  /* currentColor, not var(--sh-text): if anything ever recolours the button the
     bars follow it instead of disappearing into it. */
  content:""; position:absolute; left:11px; width:22px; height:2px;
  background:currentColor; border-radius:2px;
  transition:transform 250ms var(--sh-ease), opacity 200ms var(--sh-ease);
}
.sh-header__toggle-bar{ top:21px; }
.sh-header__toggle-bar::before{ left:0; top:-7px; }
.sh-header__toggle-bar::after{ left:0; top:7px; width:14px; }
.sh-header__toggle[aria-expanded="true"] .sh-header__toggle-bar{ transform:rotate(45deg); }
.sh-header__toggle[aria-expanded="true"] .sh-header__toggle-bar::before{ opacity:0; }
.sh-header__toggle[aria-expanded="true"] .sh-header__toggle-bar::after{ width:22px; transform:rotate(-90deg) translateX(7px); }

@media (max-width:860px){
  .sh-header .sh-header__toggle{ display:block; }
  .sh-nav{
    position:absolute; left:0; right:0; top:100%;
    flex-direction:column; align-items:stretch; gap:0;
    /*
     * Opaque, not 96% + a blur. backdrop-filter does not apply here — the panel
     * sits inside the header's own stacking context — so the 4% let the hero
     * headline read straight through the menu. A navigation panel you can read
     * the page through looks broken rather than layered.
     */
    background:#070707;
    background-image:linear-gradient(180deg, #0b0b0b 0%, #050505 100%);
    border-bottom:1px solid var(--sh-hairline);
    padding:8px clamp(16px, 3vw, 40px) 24px;
    /* Closed by default; opened by the toggle. Never trapped closed without JS —
       the toggle is only shown at this width and always restores. */
    display:none;
  }
  .sh-nav.sh-open{ display:flex; }
  .sh-nav__list{ flex-direction:column; align-items:stretch; gap:0; }
  .sh-nav__list a{ padding:16px 0; font-size:16px; border-bottom:1px solid var(--sh-hairline); display:block; }
  .sh-nav__list .current-menu-item > a::after{ content:none; }
  .sh-nav__list .current-menu-item > a::after{ content:none; }
  .sh-header__cta{ margin-top:20px; padding:15px 22px; font-size:15px; }
}

.sh-footer__inner{ max-width:var(--sh-container); margin-inline:auto; padding:56px clamp(20px, 3vw, 32px) 32px; }
.sh-footer__grid{
  /*
   * Four columns since the Explore list was added. The footer previously reached
   * Pricing and Contact but not Work or About — so the two pages a visitor is
   * most likely to want next had no route from the bottom of any page, which is
   * exactly where people look once they have finished reading one.
   */
  display:grid; grid-template-columns:2fr 1fr 1fr 1fr; gap:32px; margin-bottom:40px;
}
.sh-footer__logo{ width:150px; }
.sh-footer__tagline{ color:var(--sh-text-3); font-size:12px; line-height:1.6; margin:14px 0 0; max-width:240px; }
.sh-footer__col ul{ list-style:none; margin:12px 0 0; padding:0; display:flex; flex-direction:column; gap:8px; }
.sh-footer__col a{ font-size:14px; }
.sh-footer__base{
  border-top:1px solid var(--sh-hairline); padding-top:20px;
  color:var(--sh-text-3); font-size:12px;
  display:flex; flex-wrap:wrap; align-items:center; gap:6px 20px;
}
.sh-footer__legal{ color:var(--sh-text-3); }
.sh-footer__legal:hover{ color:var(--sh-text); }

/* ------------------------------------------------------------ long prose --- */
/*
 * Only the privacy policy uses this. Elementor's text editor emits bare <p>,
 * which at 18px/1.5 with no paragraph spacing reads as one block; and the
 * default link colour is invisible against the body grey.
 */
.sh-prose p{ margin:0 0 1.05em; }
.sh-prose p:last-child{ margin-bottom:0; }
.sh-prose strong{ color:var(--sh-text); font-weight:500; }
.sh-prose a{
  color:var(--sh-text); text-decoration:underline;
  text-decoration-color:var(--sh-hairline-strong);
  text-underline-offset:3px;
}
.sh-prose a:hover{ text-decoration-color:currentColor; }

/*
 * Reading measure, for the policy only.
 *
 * The column runs to 820px, which at 18px puts roughly 95 characters on a line.
 * Comfortable prose is 60-75; past about 80 the eye loses the start of the next
 * line and long documents become genuinely hard to get through. `ch` rather than
 * pixels so the measure holds if the type scale ever changes.
 *
 * Scoped to .sh-policy, NOT to .sh-prose: the same prose class carries the
 * project write-up, whose gallery has to keep the full column width.
 */
/* Belt and braces behind the Elementor width control on each block. */
.e-con > .sh-policy{ max-width:68ch; }
@media (max-width:767px){
  /* Two up on a phone rather than one long ladder of links. The brand block
     keeps the full width above them. */
  .sh-footer__grid{ grid-template-columns:repeat(2, minmax(0,1fr)); gap:28px 20px; }
  .sh-footer__brand{ grid-column:1 / -1; }
  .sh-footer__inner{ padding-top:48px; }
}

/* ------------------------------------------------------------- the hero --- */
/*
 * The opening frame carries the brand on its own, so the header lets go of the
 * wordmark while the hero is on screen: no chrome, no duplicate logo, nothing
 * competing with the headline. The header materialises on scroll.
 *
 * The design system's rule is explicit — one dominant identifier per layout —
 * and this is how the hero wordmark and the header wordmark stop fighting.
 */
/* The header has a single state: solid, in flow, wordmark visible. */
.sh-header__logo{ transition:opacity 500ms var(--sh-ease); }
.sh-header{
  transition:background-color 400ms var(--sh-ease), border-color 400ms var(--sh-ease),
             backdrop-filter 400ms var(--sh-ease);
}


/* ============================================ loop grid & taxonomy filter = */
/* Elementor Pro renders these; the brand layer only re-skins them so the live
   Projects grid is indistinguishable from the hand-built tiles it replaced. */

.sh-loop .elementor-loop-container{ gap:24px !important; }
@media (max-width:767px){ .sh-loop .elementor-loop-container{ gap:16px !important; } }
.sh-loop .e-loop-item{ display:flex; }
.sh-loop .e-loop-item > .e-con{ width:100%; }

/* The whole card is a link; keep the underline language out of it. */
.sh-loop .sh-tile a{ border:0; text-decoration:none; }
.sh-loop .sh-tile h3 a{ color:var(--sh-text); }

/* Cover photo settles as the card is hovered — the only movement on a tile. */
.sh-tile .sh-media{ transition:transform 900ms var(--sh-ease), opacity 500ms var(--sh-ease); }
.sh-tile:hover .sh-media{ transform:scale(1.025); }

/* The index numeral sits inline with the category eyebrow. */
.sh-index-inline{ font-size:12px; color:var(--sh-text-3); letter-spacing:-0.05em; }

/* Pro's filter, wearing the system's chips.
   Elementor registers `widget-loop-filter` and enqueues it when the widget
   renders — i.e. AFTER the theme stylesheet — so at equal specificity its
   defaults win. These declarations are marked important deliberately: this is a
   re-skin of a third-party widget, not a style preference. */
.sh-taxfilter .e-filter{
  display:flex !important; flex-wrap:wrap !important;
  gap:8px !important; justify-content:flex-start !important;
}
.sh-taxfilter .e-filter-item{
  appearance:none !important; cursor:pointer;
  font-family:Inter, system-ui, sans-serif !important;
  font-size:13px !important; font-weight:500 !important; line-height:1 !important;
  letter-spacing:-0.05em !important;
  padding:11px 20px !important;
  border-radius:var(--sh-radius-pill) !important;
  background-color:transparent !important;
  color:var(--sh-text-2) !important;
  border:1px solid var(--sh-hairline) !important;
  box-shadow:none !important;
  transition:color 250ms var(--sh-ease), border-color 250ms var(--sh-ease),
             background-color 250ms var(--sh-ease) !important;
}
.sh-taxfilter .e-filter-item:hover{
  color:var(--sh-text) !important; border-color:var(--sh-hairline-strong) !important;
}
.sh-taxfilter .e-filter-item[aria-pressed="true"]{
  background-color:var(--sh-ivory) !important;
  color:var(--sh-black) !important;
  border-color:var(--sh-ivory) !important;
}

/* Project page: the write-up, and the gallery lightbox trigger. */
.sh-prose{ max-width:640px; color:var(--sh-text-2); }
.sh-prose p{ margin:0 0 1.1em; }
.sh-prose h2, .sh-prose h3{ color:var(--sh-text); margin:2em 0 .6em; }
.sh-gallery .e-gallery-item{ border-radius:var(--sh-radius-md); overflow:hidden; }
.sh-gallery .e-gallery-image{ transition:transform 900ms var(--sh-ease); }
.sh-gallery .e-gallery-item:hover .e-gallery-image{ transform:scale(1.03); }

/* A project page opens with type only, so it needs less headroom than the home hero. */
.sh-stage--hero-sm{ }

/* ------------------------------------------------------- portfolio cadence -- */
/*
 * A flat 3-across grid reads like a stock listing page. Giving the leading
 * project a wide, cinematic frame and letting the rest fall into threes creates
 * an editorial rhythm — and because it keys off :first-child, whichever project
 * leads the filtered set becomes the feature. Nothing is hard-coded per project.
 */
/* Elementor interleaves per-item <style> elements between the loop items inside
   the grid container, so :first-child and :nth-child() land on a <style> tag.
   :first-of-type / :nth-of-type() count only the DIVs, i.e. the real items. */
@media (min-width:768px){
  /*
   * The leading card used to span two columns to create hierarchy. The featured
   * band above the portfolio now does that job explicitly and with the client's
   * own choice of shoot, so the archive below is a clean, even grid — two
   * competing "hero" cards on one page read as a mistake.
   *
   * What survives is the quiet rhythm: every sixth frame goes square. Written as
   * 6n rather than 6 so it keeps repeating as the portfolio grows past one page.
   */
  .sh-loop--work .elementor-loop-container > .e-loop-item:nth-of-type(6n) .sh-ratio-work{
    aspect-ratio:1/1;
  }
}

/* ------------------------------------------------------- featured band ----- */
/*
 * The top tier of the Work page. Two across on desktop, one on phones, with a
 * wider frame and more type than a portfolio card — the difference has to be
 * legible at a glance or the band is just a grid with a label on it.
 */
.sh-ratio-feature{ aspect-ratio:16/10; }
.sh-ratio-feature > img{ width:100%; height:100%; object-fit:cover; }
@media (max-width:767px){
  .sh-ratio-feature{ aspect-ratio:4/3; }
}

.sh-card--feature{
  background:var(--sh-graphite);
  transition:transform .5s cubic-bezier(.22,.61,.36,1), border-color .5s;
}
.sh-card--feature:hover{ border-color:var(--sh-text-3); }
.sh-card--feature .sh-card__footer{ margin-top:6px; }
.sh-card--feature .sh-card__footer p{ margin:0; }
/* Both fields are optional; an empty one must not leave a gap or a stray rule. */
.sh-card--feature .sh-card__footer > .e-con:empty,
.sh-card--feature .sh-card__footer .elementor-widget-text-editor:empty{ display:none; }

.sh-loop--feature .elementor-loop-container{
  align-items:stretch;
  /*
   * Rows size to their own content. Elementor's "equal height" control is off on
   * this grid for the same reason — as `grid-auto-rows:1fr` it made every row as
   * tall as the tallest card in the WHOLE grid, so a full-width closing card left
   * the two above it with 190px of dead space. Grid items still stretch to their
   * own row, which is the part that was actually wanted.
   */
  grid-auto-rows:auto;
}
/*
 * An odd number of featured shoots would otherwise leave one card marooned in a
 * half-empty row. Letting it run the full width reads as a deliberate closing
 * frame instead, and means the client can tick three or five without the band
 * looking broken. :nth-of-type, not :nth-child — Elementor interleaves per-item
 * <style> tags between the loop items.
 */
@media (min-width:768px){
  .sh-loop--feature .elementor-loop-container > .e-loop-item:last-of-type:nth-of-type(odd){
    grid-column:1 / -1;
  }
  .sh-loop--feature .elementor-loop-container > .e-loop-item:last-of-type:nth-of-type(odd) .sh-ratio-feature{
    aspect-ratio:25/11;
  }
  /* One featured shoot on its own is the band; it should not also be the odd one out. */
  .sh-loop--feature .elementor-loop-container > .e-loop-item:only-of-type .sh-ratio-feature{
    aspect-ratio:25/11;
  }
}

/* The band's own label, so the two tiers are never mistaken for one long grid. */
.sh-tier__head{ display:flex; align-items:baseline; justify-content:space-between; gap:16px; flex-wrap:wrap; }

/* ------------------------------------------------ new media proportions ---- */
.sh-ratio-band{ aspect-ratio:12/5; }
.sh-ratio-portrait{ aspect-ratio:4/5; }
@media (max-width:767px){
  .sh-ratio-band{ aspect-ratio:4/3; }
  .sh-ratio-portrait{ aspect-ratio:3/4; }
}

/* --------------------------------------------------- client logo strip ----- */
/* Real client artwork: near-black on transparency, so it only ever sits on an
   ivory band. Held to one width so the strip reads as a set rather than as six
   differently-scaled files, and knocked back so it supports the page instead of
   competing with the wordmark. */
.sh-logo-strip{ row-gap:36px !important; }
.sh-client-logo img{
  display:block; width:100%; height:auto;
  opacity:.62;
  filter:grayscale(1);
  transition:opacity 320ms var(--sh-ease);
}
.sh-client-logo:hover img{ opacity:1; }
@media (max-width:767px){
  .sh-logo-strip{ gap:28px !important; }
}

/* Body copy on the About page keeps the system's measure. */
.sh-invert .sh-eyebrow, .sh-invert .sh-eyebrow *{ color:var(--sh-text-3); }

/* ============================================================ column grids = */
/*
 * Percentage widths plus a flex gap overflow: four 24% columns and three 16px
 * gaps came to more than 100%, so the fourth service tile wrapped onto its own
 * row. Grid divides the space AFTER the gap instead of before it, so the count
 * is exact at every width. Elementor's own gap control still drives `gap`.
 */
.sh-cols-4, .sh-cols-3, .sh-cols-6{ display:grid !important; }
.sh-cols-4{ grid-template-columns:repeat(4, minmax(0, 1fr)); }
.sh-cols-3{ grid-template-columns:repeat(3, minmax(0, 1fr)); }
.sh-cols-6{ grid-template-columns:repeat(6, minmax(0, 1fr)); }
.sh-cols-4 > *, .sh-cols-3 > *, .sh-cols-6 > *{ width:auto !important; }

@media (max-width:1024px){
  .sh-cols-4{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
  .sh-cols-3{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
  .sh-cols-6{ grid-template-columns:repeat(3, minmax(0, 1fr)); }
}
@media (max-width:767px){
  .sh-cols-4, .sh-cols-3{ grid-template-columns:minmax(0, 1fr); }
  .sh-cols-6{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
}

/* The logo strip is a grid too, so six logos never orphan one onto a second row.
   Each logo arrives on an identical 560x200 canvas, so equal cells = equal size. */
.sh-logo-strip{ align-items:center; justify-items:center; }
.sh-client-logo{ width:100%; max-width:150px; }

/* ---------------------------------------------------- header over the hero -- */
/*
 * On the front page the header sits OVER the hero rather than above it. In flow
 * it pushed the hero down and left a hard black band with a visible seam across
 * the top of the page — the transparent state had nothing to be transparent
 * against. Fixed positioning lets the hero run to the top of the viewport.
 * Every first stage carries at least 112px of top padding, so nothing is
 * obscured by the 75px header.
 */
/* The header stays in flow — see template-parts/header.php for why. */

/* --------------------------------------------------- hero background video -- */
/*
 * Layer order matters: video at the bottom, Elementor's darkening overlay above
 * it, the film grain above that, and the copy on top. The grain tiers promote
 * their direct children to z-index 1, which would otherwise lift the video and
 * overlay above the grain and flatten the whole treatment.
 */
.sh-grain > .elementor-background-video-container,
.sh-grain-heavy > .elementor-background-video-container,
.sh-grain > .elementor-background-overlay,
.sh-grain-heavy > .elementor-background-overlay{ z-index:0; }

.sh-stage .elementor-background-video-container{ border-radius:inherit; overflow:hidden; }
.sh-stage .elementor-background-video-container video{
  width:100%; height:100%; object-fit:cover;
}

/* The hero keeps its own copy legible over the footage: the overlay strength is
   set in Elementor (editable); this only guarantees the copy sits above it. */
.sh-stage--hero > .e-con-inner,
.sh-stage--hero > .e-con{ position:relative; z-index:2; }

@media (prefers-reduced-motion:reduce){
  .sh-stage .elementor-background-video-container{ display:none; }
}

/* The floating header's transparent state must beat the solid base state; both
   are single-class selectors, so the front-page scope settles it explicitly. */


/* Elementor's image widget wrapper is a plain div; the ratio classes are applied
   to it, so the widget itself becomes the sized well. */
.elementor-widget-image.sh-media .elementor-widget-container{ height:100%; }
.elementor-widget-image.sh-media figure,
.elementor-widget-image.sh-media a{ display:block; height:100%; margin:0; }

/* ================================================= project post content ==== */
/*
 * The project gallery is a core Gallery block in the post, not an Elementor
 * widget, so that the client can actually add and reorder photos. These rules
 * dress the block to match the rest of the system: fixed 4:3 wells, the film
 * grain's radius language, and one quiet hover.
 */
.sh-prose{ color:var(--sh-text-2); }
.sh-prose > p{ max-width:640px; margin:0 0 1.2em; font-size:18px; line-height:1.6; }
.sh-prose > h2, .sh-prose > h3{ color:var(--sh-text); margin:2em 0 .6em; max-width:640px; }
.sh-prose a{ color:var(--sh-text); border-bottom:1px solid var(--sh-hairline-strong); }

.sh-prose .wp-block-gallery{
  display:grid !important;
  grid-template-columns:repeat(3, minmax(0, 1fr));
  gap:16px;
  margin:48px 0 0;
  max-width:none;
}
.sh-prose .wp-block-gallery > figure,
.sh-prose .wp-block-gallery .wp-block-image{
  margin:0; width:auto !important; flex:none !important;
  border-radius:var(--sh-radius-md);
  overflow:hidden;
  background:#141414;
  aspect-ratio:4/3;
}
.sh-prose .wp-block-gallery img{
  display:block; width:100%; height:100%; object-fit:cover;
  transition:transform 900ms var(--sh-ease), opacity 400ms var(--sh-ease);
}
.sh-prose .wp-block-gallery figure:hover img{ transform:scale(1.03); }
.sh-prose .wp-block-gallery figcaption{ display:none; }

@media (max-width:1024px){
  .sh-prose .wp-block-gallery{ grid-template-columns:repeat(2, minmax(0, 1fr)); }
}
@media (max-width:767px){
  .sh-prose .wp-block-gallery{ grid-template-columns:minmax(0, 1fr); gap:12px; margin-top:32px; }
  .sh-prose > p{ font-size:16px; }
}

/* ============================================ sticky panel stacking ======= */
/*
 * Each stage pins near the top while the next slides up over it, so a panel's
 * curved top edge sweeps across the one beneath instead of the two simply
 * meeting. It is what makes this layout style read as expensive, and the
 * overlapping shells were already the right structure for it.
 *
 * The pin offset for each stage is set in JS from that stage's own height, and
 * that is not a detail: a panel taller than the space available, pinned at a
 * fixed `top`, stops moving while its lower half is still below the fold — and
 * because it never moves again, that content becomes permanently unreachable.
 * Tall panels therefore scroll fully into view first and only then pin.
 *
 * Gated behind a body class so it can be switched off for comparison without a
 * deploy:  shwp option update sh_sticky_stack 0
 */
@media (min-width: 768px){
  /* `.e-con` also declares position:relative and ties on specificity, so the
     selector has to out-rank it. */
  body.sh-stack .sh-stage.e-con{ position:sticky; }

  /* The footer must ride OVER the last pinned panel. Stages carry z-index 1–7,
     so without this the footer would slide underneath the panel it follows. */
  body.sh-stack .sh-footer{ position:relative; z-index:60; }
}

/* Off below 768px: on a phone the panels are near-viewport-height already, and
   pinning them turns a scroll into a sequence of jumps. */
@media (max-width: 767px){
  body.sh-stack .sh-stage.e-con{ position:relative; top:auto; }
}

/* ==================================================== grain kill switch ==== */
/*
 * Grain is now OPT-IN and off by default.
 *
 * The design system treats it as a defining characteristic ("if a dark surface
 * looks clean, the tier is too low"), but it sits on TOP of the photography, and
 * the client's product IS the photography — it was visibly degrading the images.
 * A studio selling image quality cannot ship noise over its own work.
 *
 * `content:none` removes the pseudo-elements outright rather than hiding them, so
 * there is no paint cost at all when it is off.
 *
 * Re-enable site-wide:  shwp option update sh_grain 1
 */
body:not(.sh-grain-on) .sh-grain-light::before,
body:not(.sh-grain-on) .sh-grain-light::after,
body:not(.sh-grain-on) .sh-grain::before,
body:not(.sh-grain-on) .sh-grain::after,
body:not(.sh-grain-on) .sh-grain-heavy::before,
body:not(.sh-grain-on) .sh-grain-heavy::after{
  content:none !important;
}

/* With the noise gone the atmosphere sweep carries the lighting on its own, so it
   is warmed very slightly to keep dark surfaces from reading as flat black. */
body:not(.sh-grain-on) .sh-atmosphere{
  background-image:radial-gradient(140% 105% at 26% -16%, #3f3f3f 0%, #292929 32%, #161616 62%, #090909 100%);
}

/* ================================================= feature media on wide === */
/*
 * The hero photograph and the editorial band are the two images meant to be
 * cinematic, but they were capped at the 1200px text measure — on a 1920 display
 * that left 360px of dead space either side and made the photography look small.
 * Text stays at its measure; only these two break wider.
 */
@media (min-width: 1600px){
  .sh-media.sh-ratio-wide,
  .sh-media.sh-ratio-band{
    width:min(1560px, 88vw);
    /* A `max-width:100%` further up the cascade clamps this straight back to the
       1200px measure, which is the whole thing being escaped. Marked important
       because it is a deliberate break-out, not a preference. */
    max-width:none !important;
    margin-inline:calc((1200px - min(1560px, 88vw)) / 2);
  }
}

/* ============================================== mobile booking bar ========= */
/*
 * A persistent booking action on phones. This is a booking-led business and the
 * header CTA disappears behind the menu toggle on mobile, so the primary action
 * was two taps away from every scroll position. Call is beside it because a good
 * share of enquiries for this trade come by phone.
 */
.sh-mobilebar{ display:none; }

@media (max-width: 767px){
  .sh-mobilebar{
    display:flex; gap:10px; align-items:center;
    position:fixed; left:0; right:0; bottom:0; z-index:70;
    padding:10px 14px calc(10px + env(safe-area-inset-bottom));
    background:rgba(5,5,5,.86);
    -webkit-backdrop-filter:saturate(140%) blur(14px);
    backdrop-filter:saturate(140%) blur(14px);
    border-top:1px solid var(--sh-hairline);
  }
  .sh-mobilebar__book,
  .sh-mobilebar__call{
    display:inline-flex; align-items:center; justify-content:center;
    min-height:48px; border-radius:var(--sh-radius-pill);
    font-size:15px; font-weight:500; letter-spacing:-0.04em; line-height:1;
    text-decoration:none; border:0;
  }
  .sh-mobilebar__book{
    flex:1 1 auto; background:var(--sh-ivory); color:var(--sh-black) !important;
  }
  .sh-mobilebar__call{
    flex:0 0 auto; padding:0 20px;
    background:transparent; color:var(--sh-text) !important;
    border:1px solid var(--sh-hairline-strong);
  }
  /* Keep the bar from covering the end of the footer. */
  body{ padding-bottom:76px; }
}

@media (prefers-reduced-motion: reduce){
  .sh-mobilebar{ -webkit-backdrop-filter:none; backdrop-filter:none; background:rgba(5,5,5,.96); }
}

/* ===================================================================== 404 = */
.sh-404__stage{
  display:flex; align-items:center; justify-content:center;
  min-height:70svh;
  padding:clamp(96px, 12vw, 168px) clamp(20px, 4vw, 48px);
  text-align:center;
}
.sh-404__inner{ max-width:620px; }
.sh-404__title{
  font-size:clamp(2.125rem, 1.2rem + 3.4vw, 3.25rem);
  font-weight:400; line-height:1.1; letter-spacing:-0.036em;
  color:var(--sh-text); margin:20px 0 18px;
}
.sh-404__lead{ color:var(--sh-text-2); font-size:18px; line-height:1.55; margin:0 0 36px; }
.sh-404__actions{ display:flex; flex-wrap:wrap; gap:12px; justify-content:center; }
.sh-404__btn{
  display:inline-flex; align-items:center; justify-content:center;
  min-height:46px; padding:0 24px; border-radius:var(--sh-radius-pill);
  font-size:15px; font-weight:500; letter-spacing:-0.04em; line-height:1;
  color:var(--sh-text) !important; text-decoration:none;
  border:1px solid var(--sh-hairline-strong);
  transition:border-color 250ms var(--sh-ease), background-color 250ms var(--sh-ease);
}
.sh-404__btn:hover{ border-color:var(--sh-hairline-accent); }
.sh-404__btn--primary{
  background:var(--sh-ivory); color:var(--sh-black) !important; border-color:var(--sh-ivory);
}

/* ============================================== mobile tap targets ======== */
/*
 * Footer links measured 17px tall on a phone — under the 24px WCAG 2.5.8 minimum
 * and genuinely fiddly with a thumb, especially the email and phone links, which
 * are the two a visitor is most likely to reach for. Padding grows the hit area
 * without opening visual gaps, and the negative margin keeps the list looking the
 * same as it does now.
 */
@media (max-width: 767px){
  .sh-footer__col ul{ gap:0; }
  .sh-footer__col a{
    display:flex; align-items:center;
    min-height:44px;
    margin-block:-2px;
  }
  .sh-footer__base{ padding-block:20px; }
  /* The legal link sits on the base line, outside .sh-footer__col, so it needs
     the 44px target spelled out for it too. */
  .sh-footer__legal{ display:inline-flex; align-items:center; min-height:44px; }

  /* The logo anchor was shrink-wrapping to the image's intrinsic box. */
  .sh-header__logo{ display:flex; align-items:center; min-height:44px; }

  /* Form controls: comfortable to hit, and 16px so iOS does not zoom on focus. */
  .sh-form input:not([type=checkbox]):not([type=radio]),
  .sh-form select{
    min-height:48px !important;
    font-size:16px !important;
  }
  .sh-form textarea{ font-size:16px !important; }
}


/* ===================================================== project page extras === */
/*
 * The listing film and the fact strip. Both are optional per project, so both
 * have to disappear completely — not collapse to a thin empty band — when the
 * field behind them is blank.
 */

/* A stage whose only content is an empty shortcode still paints its background
   and its padding, which reads as a mystery gap between the cover and the
   write-up. Hide the whole stage when the film did not render. */
.sh-no-film .sh-stage--film{ display:none; }
.sh-stage--film:not(:has(.sh-film)){ display:none; }  /* belt and braces */

.sh-film{
  position:relative; width:100%; aspect-ratio:16/9; overflow:hidden;
  border-radius:36px; background:var(--sh-near-black);
  border:1px solid var(--sh-hairline);
}
.sh-film__play{
  position:absolute; inset:0; width:100%; height:100%; padding:0; margin:0;
  border:0; background:none; cursor:pointer; display:block;
}
.sh-film__still{ width:100%; height:100%; object-fit:cover; display:block; }
.sh-film__icon{
  position:absolute; top:50%; left:50%; transform:translate(-50%,-50%);
  width:84px; height:84px; border-radius:50%;
  background:rgba(12,12,12,.55); backdrop-filter:blur(6px);
  border:1px solid rgba(243,245,243,.35);
  transition:transform .4s cubic-bezier(.22,.61,.36,1), background .4s;
}
.sh-film__icon::after{
  content:""; position:absolute; top:50%; left:54%; transform:translate(-50%,-50%);
  border-style:solid; border-width:11px 0 11px 18px;
  border-color:transparent transparent transparent var(--sh-text);
}
.sh-film__play:hover .sh-film__icon{ transform:translate(-50%,-50%) scale(1.08); background:rgba(12,12,12,.75); }
.sh-film__play:focus-visible{ outline:2px solid var(--sh-text); outline-offset:4px; }
.sh-film iframe{ position:absolute; inset:0; width:100%; height:100%; border:0; }
@media (max-width:767px){
  .sh-film{ border-radius:20px; }
  .sh-film__icon{ width:62px; height:62px; }
  .sh-film__icon::after{ border-width:9px 0 9px 14px; }
}

/* The fact strip: label above value, wrapping to as many columns as fit. */
.sh-facts{
  /* Flex, not grid. `repeat(auto-fit, minmax(150px, max-content))` laid out four
     facts into six tracks on a 1440 screen, two of them zero-width, which threw
     the spacing between the visible ones. Flex sizes to the content it has. */
  display:flex; flex-wrap:wrap; gap:22px 56px; margin:0; padding:0;
  border-top:1px solid var(--sh-hairline); padding-top:24px;
}
.sh-facts__row{ display:flex; flex-direction:column; gap:6px; }
.sh-facts dt{
  font-size:12px; font-weight:500; letter-spacing:.12em; text-transform:uppercase;
  color:var(--sh-text-3); margin:0;
}
.sh-facts dd{ margin:0; font-size:16px; color:var(--sh-text-2); line-height:1.4; }
@media (max-width:767px){
  .sh-facts{ display:grid; grid-template-columns:repeat(2, minmax(0,1fr)); gap:20px; padding-top:20px; }
  .sh-facts dd{ font-size:15px; }
}


/* Elementor's own filter chips render at 37px, which is under the 44px minimum
   touch target. The chips are the only way to navigate the portfolio on a phone. */
@media (max-width:767px){
  .sh-taxfilter .e-filter-item{
    min-height:44px; display:inline-flex; align-items:center;
  }
}


/* ------------------------------------------------------- load more button -- */
/*
 * Elementor renders the loop's Load More as a bare `.elementor-button
 * .elementor-size-sm` with no styling of its own. Left alone it is a blue-grey
 * default button at the bottom of the portfolio. Matched to the site's secondary
 * capsule instead — this is the control the client's visitors will use most once
 * there are more than nine shoots.
 */
.e-loop__load-more{ display:flex; justify-content:center; margin-top:40px; }
.e-loop__load-more .elementor-button{
  background:transparent; color:var(--sh-text);
  border:1px solid var(--sh-hairline-strong);
  min-height:48px; padding:0 30px; display:inline-flex; align-items:center;
}
.e-loop__load-more .elementor-button:hover{
  border-color:var(--sh-hairline-accent); background:rgba(243,245,243,.04);
}
.e-load-more-message{ text-align:center; color:var(--sh-text-3); font-size:14px; }


/* --------------------------------------------- remaining mobile targets ---- */
/*
 * Two standalone controls were still under the 44px minimum on a phone.
 *
 * NOT included, deliberately: the links inside sentences on the privacy policy.
 * WCAG 2.2 SC 2.5.8 exempts inline targets precisely because forcing 44px onto a
 * link in a paragraph breaks the line rhythm around it, and those links all have
 * a plain-text equivalent beside them anyway.
 */
@media (max-width:767px){
  /* Pricing property-size chips: 42px, two short of the mark. */
  .sh-filter .elementor-button{
    min-height:44px !important;
    display:inline-flex !important; align-items:center !important;
  }

  /* Contact page email and phone. These sit on their own under a label, so they
     are standalone targets, not inline ones — and they are the two things a
     person on a phone is most likely to reach for. */
  .sh-contact-detail a{
    display:inline-flex; align-items:center; min-height:44px;
  }
}


/* ================================================ mobile flow corrections === */

/*
 * The hero wordmark is a title-card device and works at desktop width. On a phone
 * the header wordmark sits ~60px above it, so the first screen opened with the
 * brand written twice and pushed the headline down the fold. The header keeps it;
 * the hero lets go.
 */
@media (max-width:767px){
  .sh-hero-wordmark{ display:none !important; }
}

/*
 * Hero copy sits on moving footage, so it cannot rely on a flat background for
 * contrast. The stage already carries a 66% scrim; this is the second line of
 * defence for a bright frame — a shadow tuned to be invisible on dark footage and
 * to hold the letterforms together on a blown-out sky.
 */
.sh-stage--hero h1,
.sh-stage--hero .sh-hero-lead{
  text-shadow:0 1px 24px rgba(0,0,0,.55), 0 1px 3px rgba(0,0,0,.35);
}

/*
 * Category chips came out 68px tall on a phone: min-height:44px on top of
 * Elementor's own 11px/20px padding and line-height, which stack rather than
 * overlap. Height, not min-height, so the 44px target is exact.
 */
@media (max-width:767px){
  .sh-taxfilter .e-filter-item{
    min-height:0; height:44px; padding-block:0;
    display:inline-flex; align-items:center;
  }
}

/*
 * Cards flash as empty rounded voids while their photograph decodes, which on a
 * slow phone connection is most of the first scroll. A tone inside the frame makes
 * the wait read as a loading image rather than a broken one.
 */
.sh-ratio-work, .sh-ratio-feature, .sh-ratio-wide, .sh-ratio-band, .sh-ratio-portrait{
  background-color:#141414;
}
.sh-invert .sh-ratio-work, .sh-invert .sh-ratio-wide{ background-color:#e8eae8; }

/*
 * The sticky booking bar is hidden while the hero is on screen.
 *
 * The hero already carries "Book a shoot" as its primary button, so the bar put a
 * second identical call to action ~200px below the first — and with the menu open,
 * a third. One ask per screen. The bar earns its place further down the page,
 * where the hero button is long gone; `initMobileBar` adds .sh-bar-on past it.
 */
/*
 * Gated on `.sh-bar-js`, which the script adds to <body> itself. If the script
 * never runs, the class is never added and the bar behaves exactly as it did
 * before — always visible — rather than being hidden forever by CSS that is
 * waiting for JS that is not coming.
 */
@media (max-width: 767px){
  body.sh-bar-js .sh-mobilebar{
    transform:translateY(110%);
    transition:transform 320ms var(--sh-ease), opacity 320ms var(--sh-ease);
    opacity:0; pointer-events:none;
  }
  body.sh-bar-js.sh-bar-on .sh-mobilebar{
    transform:none; opacity:1; pointer-events:auto;
  }
}
@media (max-width:767px) and (prefers-reduced-motion:reduce){
  .sh-mobilebar{ transition:none; }
}


/*
 * Property-size switch on a phone.
 *
 * Three pills in a wrapping flex row broke as 2 + 1, with the odd one centred
 * underneath — which reads as a mistake rather than a control, and made "Large"
 * look like a different kind of thing from the other two. One row, equal widths,
 * so it reads as the segmented control it actually is.
 */
@media (max-width:767px){
  .sh-switch{ flex-wrap:nowrap; gap:6px; }
  /* The widget wrapper carries 10px/18px of its own padding, which left the
     button only 58px of content box to sit in — `width:100%` then resolved
     against 58px and clipped "Apartment" to "Apartme". */
  .sh-switch .sh-filter{ flex:1 1 0; min-width:0; padding:0 !important; }
  .sh-switch .sh-filter .elementor-button{
    width:100%; height:44px; padding:0 6px !important;
    font-size:12.5px !important; white-space:nowrap;
    display:flex; align-items:center; justify-content:center;
  }
}

/* The bar is removed on the booking page, so the space reserved for it goes too. */
@media (max-width: 767px){
  body.sh-bar-off{ padding-bottom:0; }
}


/* ------------------------------------------------------------ proof band --- */
/*
 * Three facts under the hero photograph. A hairline above each one so they read
 * as a set of related claims rather than three loose paragraphs — the rule is
 * what makes it a table of facts instead of a column of text.
 */
.sh-fact{ border-top:1px solid var(--sh-hairline); padding-top:18px; }
.sh-fact .elementor-heading-title{ letter-spacing:-0.03em; }
@media (max-width:767px){
  .sh-facts-row{ gap:22px !important; }
  .sh-fact{ padding-top:14px; }
}


/*
 * Booking-form honeypot (see mu-plugins/sh-hardening.php).
 *
 * Lives here, not in an injected <style>, because it must be present on every
 * render without exception — while this rule was missing the decoy "Website"
 * field was VISIBLE on the live booking form, which is worse than having no
 * honeypot at all: a real agent could fill it in and silently block their own
 * enquiry.
 *
 * Off-screen rather than display:none — the better bots skip hidden inputs.
 */
/*
 * Target the FIELD GROUP, not just the input.
 *
 * Elementor's per-field `css_classes` lands on the <input> only, so the first
 * version parked the input off-screen and left its "Website" label sitting in the
 * middle of the booking form with nothing under it. The group class Elementor
 * derives from the field id (`elementor-field-group-sh_website`) is what has to
 * move, and it takes the label with it.
 */
.elementor-field-group-sh_website,
.sh-hp{
  position:absolute !important; left:-9999px !important; top:auto !important;
  width:1px !important; height:1px !important;
  overflow:hidden !important;
  opacity:0 !important; pointer-events:none !important;
}
