/* ---------------------------------------------------------------------------
   Patches applied on top of the production build.

   There is no source repository for this site -- the server holds a minified
   Vite build and nothing else -- so fixes that would normally live in a
   component are collected here instead. Loaded last, after the generated
   bundles, so these rules win without any bundle being edited.

   Every block states the defect it corrects.
   --------------------------------------------------------------------------- */

/* 1. Desktop navigation cut off the right edge between 1024px and 1359px.
      ---------------------------------------------------------------
      The bar switches from the burger to the full link list at Tailwind's
      `lg` breakpoint (1024px), but the list needs about 1271px of layout
      width. In between, the nav is 1090px wide inside a 1019px parent: at a
      1100px window "CONTACT US" ended 110px off-screen and the theme toggle
      171px off-screen, both unreachable.

      The obvious fix -- keep the burger until there is room -- does not work
      here. The slide-out panel is not merely `lg:hidden`; React does not
      render it at all above 1024px, so forcing the button back on produced a
      burger that opened nothing. CSS cannot mount a component.

      So make the bar fit instead. The width is spent on decoration, not
      content: 64px of `lg:pr-16` against the right edge, 20px of padding on
      each side of all seven links, 12px between them, and 0.25em of letter
      spacing. Trimming those recovers roughly 250px -- more than the ~186px
      needed -- without dropping a single link.

      Everything is scoped to the nav container (`.flex-1` distinguishes it
      from the theme-toggle wrapper, which shares `lg:flex`). */
@media (min-width: 1024px) and (max-width: 1359px) {
  /* padding held against the screen edge, for nothing */
  [class*='lg:flex'][class*='flex-1'] {
    padding-right: 0 !important;
  }

  /* the pill holding the links */
  [class*='lg:flex'][class*='flex-1'] [class*='gap-3'] {
    gap: 0.25rem !important;
  }

  /* each link: tighter padding and letter spacing, same type size */
  [class*='lg:flex'][class*='flex-1'] a[class*='px-5'] {
    padding-left: 0.45rem !important;
    padding-right: 0.45rem !important;
    letter-spacing: 0.14em !important;
  }

  /* the theme toggle's own wrapper, which sits after the pill on `ml-4`.
     At 1024px -- the tightest point, where the container is narrowest -- the
     toggle was still 11px over the edge without this. */
  [class*='lg:flex'][class*='ml-4'] {
    margin-left: 0.5rem !important;
  }
}

/* 2. "Our Legacy" photo card sliced off the right edge of the screen.
      ---------------------------------------------------------------
      The section is laid out as `grid lg:grid-cols-12 gap-16 lg:gap-32`
      holding two `lg:col-span-6` children. A 12-track grid has ELEVEN
      gutters, and `lg:gap-32` makes each one 8rem:

          11 x 128px = 1408px of gutter, before any content

      Tailwind's `container` caps the width at 1024px (lg) / 1280px (xl), so
      the gutters alone cannot fit. Every track collapses -- the computed
      value is literally `0px 0px 0px 0px 0px 0px 0px 0px 0px 0px 0px 0px` --
      and the items are then positioned by the gutter arithmetic, which pushes
      the second column far past the container. Measured in an 1100px window,
      the right-hand column ran from x=823 to x=1463 against a 1085px
      viewport, so the photo card overflowed by 384px. The section is
      `overflow: hidden`, so it is sliced rather than scrolled.

      It is wrong from 1024px up to 1535px. Only at 2xl does the container
      (1488px of content) finally exceed the 1408px of gutter.

      The layout only ever wanted two columns side by side, and two columns
      need one gutter, not eleven. Declare that directly. */
@media (min-width: 1024px) {
  #philosophy [class*='grid-cols-12'] {
    grid-template-columns: repeat(2, minmax(0, 1fr));
    column-gap: clamp(2rem, 4vw, 8rem);
  }

  /* the children carry `lg:col-span-6`, which would span both new tracks */
  #philosophy [class*='grid-cols-12'] > * {
    grid-column: auto;
  }
}

/* The card is `absolute right-0` around an image sized intrinsically up to
   `max-w-[600px]`, so on a narrow column it still reaches past the edge.
   Tie it to the column it lives in. */
#philosophy .absolute.top-0.right-0 {
  max-width: 100%;
}

#philosophy .absolute.top-0.right-0 > img {
  max-width: 100%;
  height: auto;
}

/* 3. Brand marquee: spacing, and the styling for the drag behaviour.
      ---------------------------------------------------------------
      Each logo sits in a 102px box inside a tile forced to `min-w-[9rem]`
      (144px, 160px at sm), and the tiles are then separated by `gap-8
      md:gap-12`. So the visible gap between two logos was the 48px gutter
      plus 26px of empty tile on each side -- about 74px between 102px logos,
      which is why the strip read as mostly background.

      Dropping the min-width lets each tile be its logo, and a 2rem gutter
      then reads as deliberate spacing rather than a gap. */
[class*='w-max'][class*='whitespace-nowrap'] {
  column-gap: 2rem;
}

[class*='w-max'][class*='whitespace-nowrap'] > [class*='min-w-['] {
  min-width: 0;
}

/* The marquee is converted into a real scroll container by js/patches.js so
   that dragging, the wheel and touch all work. Hide the scrollbar it gains. */
.ph-marquee-wrapper {
  overflow-x: auto;
  cursor: grab;
  scrollbar-width: none;
  -ms-overflow-style: none;
  overscroll-behavior-x: contain;
}

.ph-marquee-wrapper::-webkit-scrollbar {
  display: none;
}

.ph-marquee-wrapper.is-dragging {
  cursor: grabbing;
}

/* Without this the browser's own image/link dragging fights the pointer
   handler, and a horizontal drag can turn into a page swipe on touch. */
.ph-marquee-wrapper.is-dragging,
.ph-marquee-wrapper.is-dragging * {
  user-select: none;
}

.ph-marquee-wrapper img,
.ph-marquee-wrapper a {
  -webkit-user-drag: none;
}

.ph-marquee-track {
  transform: none !important;
  touch-action: pan-x;
  /* was py-8 md:py-12 lg:py-16 -- 64px of padding above and below the logos */
  padding-top: 1rem !important;
  padding-bottom: 1rem !important;
}

/* The partner strip: heading, and far less air around it.
   ---------------------------------------------------------------
   The section stood 600px tall to present a 179px strip of logos, and
   carried no heading at all, so it read as decoration rather than as a
   statement about who the company works with. The height came from four
   stacked spacers: 128px of section padding top and bottom
   (`py-16 md:py-24 lg:py-32`), 64px inside the track itself, an 80px
   `lg:mb-20` under the strip and a 24px `mb-6` under its wrapper.

   The heading is inserted by js/patches.js using the same class list as the
   "Sectors" eyebrow elsewhere on the page. */
.ph-partners-section {
  padding-top: clamp(2.5rem, 4vw, 3.5rem) !important;
  padding-bottom: clamp(2.5rem, 4vw, 3.5rem) !important;
}

.ph-partners-inner {
  margin-bottom: 0 !important;
}

/* the rule directly beneath now carries the gap down to the logos */
.ph-partners-title {
  margin-bottom: 0;
}

.ph-marquee-wrapper {
  margin-bottom: 0 !important;
}

/* 5. "How we Deliver Every Project" cards: one even overlay, aligned rows.
      ---------------------------------------------------------------
      Two separate faults here.

      The overlay was built from four layers, two of which covered only part
      of the card: a wash over the bottom half, and a solid `h-36 bg-black/30`
      band. A solid colour with a hard top edge draws a visible line straight
      across the photograph 144px from the foot. On top of that the picture
      itself was dimmed twice over -- `opacity: 0.4` AND
      `filter: brightness(0.46)`, about 18% of its real luminance.

      Replaced with a single even wash across the whole card. The photograph
      stays legible as texture, the text sits on a consistent base, and there
      is no edge anywhere.

      The rows did not line up either. Each card is a fixed 480px box with its
      content bottom-aligned (`flex items-end`), and the list block varies a
      lot between cards -- 140, 172, 209, 209 and 292px. Bottom-aligning
      unequal content pushes every row to a different height, which is why the
      three section labels sat at y=318, y=281 and y=236. Worse, the last
      three cards' content is TALLER than the 480px box (+9, +9 and +92px) and
      was being clipped by `overflow-hidden`.

      The title block (100px) and the description (84px) are already identical
      in every card, so top-aligning the content is enough to line up the
      icon, the heading, the body and the section label across all of them.
      Letting the card grow and fill its grid cell stops the clipping and
      keeps every card in a row the same height. */
.service-card {
  height: auto !important;
  min-height: 30rem;
  align-items: flex-start !important;
}

/* let the card fill the grid cell so a short card is not left with a gap */
[class*='group/card'] {
  display: flex;
}

[class*='group/card'] > .service-card {
  flex: 1 1 auto;
}

/* the photograph, dimmed once rather than twice */
.service-card img {
  opacity: 0.85 !important;
  filter: brightness(0.85) contrast(1.02) !important;
}

/* one even wash over the whole card, replacing the graded one */
.service-card [class*='from-[rgba(0,0,0,0.72)]'] {
  background: rgba(0, 0, 0, 0.82) !important;
  opacity: 1 !important;
}

/* the copper tint stays, quietly */
.service-card [class*='radial-gradient'] {
  opacity: 0.6 !important;
}

/* the two part-height layers that drew edges across the picture */
.service-card [class*='from-black/90'],
.service-card [class*='h-36'],
.service-card [class*='backdrop-blur-[1px]'] {
  display: none !important;
}

/* 6. Close the gap between the partner strip and the sectors section.
      ---------------------------------------------------------------
      The two sections sit flush against each other, but between the last
      logo and the "Sectors" heading there were 179px of nothing: 51px of
      padding below the strip and 128px (`py-20 lg:py-32`) above the sectors.
      Matching the strip's own padding halves it. */
section[class*='py-20'][class*='lg:py-32'] {
  padding-top: clamp(2.5rem, 4vw, 3.5rem) !important;
}

/* 7. The section eyebrows were small enough to read as captions.
      "Sectors" and "Our Partners" both sat at 13.6px. */
[class*='tracking-[0.45em]'] {
  font-size: 0.9rem !important;
}

@media (min-width: 768px) {
  [class*='tracking-[0.45em]'] {
    font-size: 1.05rem !important;
  }
}

/* 4. Sector cards: the frosted panel only blurred part of its photo.
      ---------------------------------------------------------------
      Each card layers a photo behind a text panel carrying
      `backdrop-blur-sm`, so the image reads as frosted glass. But the cards
      are grid items, which stretch to the tallest card in the row, while the
      panel inside only grows to fit its own text. The panel therefore ends
      short of the card, and the photo below that line stays perfectly sharp.

      Measured across the nine cards: every card 488px tall, panels of 486,
      486, 486, 478, 430, 408, 408, 382 and 334px -- leaving sharp strips of
      2px to 154px. The cards with the least text look the worst, which is
      why the section reads as inconsistently treated.

      Making the card a flex column and letting the panel grow ties the
      frosted layer to the card's real height, so the blur reaches the bottom
      edge on every card regardless of how much text it holds. */
[class*='rounded-[2.4rem]'] {
  display: flex;
  flex-direction: column;
}

[class*='rounded-[2.4rem]'] > [class*='backdrop-blur-sm'] {
  flex: 1 1 auto;
}

/* 8. The hero slideshow's dots followed you down the whole page.
      ---------------------------------------------------------------
      The eight slide indicators live in a host div classed
      `fixed inset-0 pointer-events-none z-20`. Because that host is fixed to
      the viewport rather than to the hero, the dots stayed pinned to the
      bottom-right corner of the screen through every section of the site --
      floating over the delivery cards, the partners strip and the sectors
      grid, all of which have nothing to do with the hero carousel.

      Their parent is the hero itself (`relative h-[100dvh]`), so switching
      the host to absolute positions them against that instead: they sit in
      the hero's bottom-right corner and scroll away with it. */
div.fixed.inset-0.pointer-events-none.z-20 {
  position: absolute !important;
}

/* 9. Footer: the same collapsed 12-track grid as the Legacy section.
      ---------------------------------------------------------------
      `grid lg:grid-cols-12 gap-16` inside a 976px container. Eleven gutters
      at 64px demand 704px before any content, so each track computed to
      22.66px. The four columns carry col-span-4 / 2 / 2 / 4, which left the
      brand column at 283px -- too narrow for "PROJECTIONHOUSE LLC", so it
      wrapped onto two lines -- and Navigation and Contact at 109px, which
      broke the address across four lines.

      The proportions were also wrong for the content: Visual Hub holds one
      small button yet was given the same width as the brand, while Contact
      had the most text and the least room. Four real tracks, weighted for
      what each actually holds, and a gutter that leaves room for them. */
@media (min-width: 1024px) {
  footer [class*='lg:grid-cols-12'] {
    grid-template-columns: 4fr 2fr 3fr 2fr;
    column-gap: 3rem;
  }

  /* the children carry lg:col-span-*, which would span the new tracks */
  footer [class*='lg:grid-cols-12'] > * {
    grid-column: auto;
  }
}

/* The footer stood 656px tall on 127px of padding top and bottom, with a
   further 96px under the columns. */
footer {
  padding-top: clamp(3.5rem, 6vw, 5rem) !important;
  padding-bottom: clamp(2.5rem, 4vw, 3.5rem) !important;
}

footer [class*='lg:grid-cols-12'] {
  margin-bottom: 3rem !important;
}

/* 10. The hairline rule above and below the partner strip.
       ---------------------------------------------------------------
       Same element the section eyebrows use:
       <span class="h-px w-16 bg-gradient-to-r from-transparent
                    to-[var(--color-copper)]">
       Both rules bracket the heading itself rather than the whole section.
       A bare <span> is inline, so it needs to be a block to take a height and
       to centre in the strip's `text-center` container. The gradient is
       restated here rather than relied upon, so the rule still draws if those
       utility classes are ever tree-shaken out of the bundle. The lower rule
       runs the other way so the pair mirrors around the section. */
.ph-partners-rule {
  display: block;
  width: 4rem;
  height: 1px;
  margin: 0 auto 0.85rem;
  background-image: linear-gradient(to right, transparent, var(--color-copper));
}

/* sits between the heading and the logos, so it carries the gap to the strip */
.ph-partners-rule--below {
  margin: 0.85rem auto 1.5rem;
  background-image: linear-gradient(to left, transparent, var(--color-copper));
}

/* 11. Hero carousel: the slide rotating in from the right was cut off.
       ---------------------------------------------------------------
       The eight slides sit on a 3D ring, so they fan out well beyond the
       300px stack that holds them -- 167px past its right edge and 99px past
       its left. But the column pins that stack to its own right edge with
       `lg:justify-end`, leaving only 48px of clearance before the viewport.

       Measured at a 1257px viewport: the active slide ended 19px inside the
       edge, but the next two ran to 1376px and 1302px -- 119px and 45px past
       it, sliced off mid-card.

       Centring the stack in its column moves it left by roughly the width of
       the overhang, and scales with the column rather than assuming a fixed
       offset. The small right padding keeps the fan clear of the edge as it
       rotates. */
@media (min-width: 1024px) {
  [class*='perspective'][class*='lg:justify-end'] {
    justify-content: center !important;
    padding-right: 2rem;
  }
}

/* 12. Header bar sat off-centre, bunched to the left.
       ---------------------------------------------------------------
       The row holds three children: the logo, the nav (flex-1), and a third
       container for the burger button. That third one carries `lg:flex-1` --
       so at exactly the breakpoint where its only child (`lg:hidden`) is
       display:none, the empty container grows and takes an equal share of the
       free space with the nav.

       Measured at 1257px: the empty container claimed 247px, which stopped
       the nav at x=965 and left a 292px hole between the theme toggle and the
       right edge, while the logo sat 33px from the left.

       An empty box should not grow. With it held to its content, the nav
       takes the free space and its own `justify-end` carries the links and
       toggle out to the edge, balancing the logo's margin on the other side. */
@media (min-width: 1024px) {
  [class*='lg:flex-1'] {
    flex: 0 0 auto !important;
  }
}

/* 13. The "20+ Years" badge sits with the menu, not the wordmark.
       ---------------------------------------------------------------
       js/patches.js moves it to the head of the nav group above 1024px and
       returns it to the wordmark below that, since the nav group is hidden on
       small screens. The group is already `items-center`, so the badge lines
       up on the same axis as the pill; this just gives it a little breathing
       room and keeps it in proportion with the 44px pill beside it. */
.ph-years-badge {
  align-self: center;
  margin-right: 0.85rem;
  flex: 0 0 auto;

  /* The badge carries `relative top-4` -- a 16px nudge down, tuned for its old
     slot beside the wordmark. Next to the pill that is exactly what threw the
     two out of line. `h-16` also made the anchor 64px around a 44px image, so
     letting it wrap its content puts it on the pill's own axis. */
  top: 0 !important;
  height: auto !important;
}

.ph-years-badge img {
  height: 2.75rem;
  width: auto;
}

/* 14. "OUR CORE OFFERINGS" is set on one line by js/patches.js.
       ---------------------------------------------------------------
       The heading is written as ["OUR", <br>, "CORE", <br>, "OFFERINGS"], so
       the breaks are hard-coded in the bundle rather than a consequence of the
       width -- but they are also the ONLY separator between the words. Hiding
       them in CSS gives "OURCOREOFFERINGS"; each has to be replaced by a real
       space, which only the script can do. Left to CSS alone the heading would
       simply keep its three lines, which is the safe fallback if the script
       never runs. */

/* 15. The badge sits with whichever control is on the right at that width:
       the nav pill above 1024px, the burger below it. Both containers are
       already `items-center`, so it lands on their axis. */
[class*='justify-end'] > .ph-years-badge {
  margin-right: 0.6rem;
}

/* 16. Light mode was unreadable: white text on a white background.
       ---------------------------------------------------------------
       The theme inverts by redefining tokens under [data-theme=light], and
       most of them are right -- --color-cream correctly flips from #e8d5a3 to
       #2b2b2b. But --color-text flips the wrong way:

           :root              --color-text: #d4af37
           [data-theme=light] --color-text: #f8f5f2   <- near-white
           [data-theme=light] --color-bg:   #fbfbfa   <- near-white

       That is a contrast ratio of 1.05 against its own background. Measured
       across the homepage in light mode, 43 of 157 text elements came out
       between 1.04 and 1.14, where AA wants 4.5 -- body copy, headings and
       list items all effectively invisible.

       Two more were too low to read as text: --color-heading and
       --color-copper both stay at #d4af37, which is 2.03 on a light ground.
       Bright gold only works against the dark theme; on white it needs
       taking down. These keep the same hue, so the brand still reads as gold.

           --color-text     #f8f5f2 -> #33302a   1.05 -> 12.70  (AAA)
           --color-heading  #d4af37 -> #7a5d0e   2.03 ->  5.97  (AA)
           --color-copper   #d4af37 -> #8a6a10   2.03 ->  4.89  (AA)

       Dark mode is untouched: every value here is scoped to the light theme. */
[data-theme='light'] {
  --color-text: #33302a;
  --color-heading: #7a5d0e;
  --color-copper: #8a6a10;
}

/* 17. In light mode the page itself stayed dark behind the content.
       ---------------------------------------------------------------
       The stylesheet gets this right -- `body { background-color:
       var(--color-bg) }` -- but the bundle injects a <style> element at
       runtime that lands after it and hardcodes the colour:

           body { font-family: var(--font-body); background: #0D1204; ... }

       Being injected later it wins on source order, so the body stayed that
       near-black green whatever the theme said, while sections that read
       --color-bg correctly turned #fbfbfa. Anywhere the body showed through,
       light mode was still dark.

       Scoped to the light theme so dark mode keeps the exact colour it has
       always had; !important because the offending rule is appended after
       this file and would otherwise win on order alone. */
[data-theme='light'] body {
  background: var(--color-bg) !important;
}

/* 18. The header did not shrink on scroll.
       ---------------------------------------------------------------
       It stands 138px tall -- a 112px logo plus 24px of padding -- and keeps
       that height the whole way down the page, which is a lot of a laptop
       viewport to give up permanently. The bar already carries
       `transition-all duration-400`, so it was built to change on scroll;
       nothing ever toggled it. js/patches.js adds the class, these rules say
       what it means, and the existing transition animates between the two.

       Roughly 138px -> 76px. Everything shrinks in proportion so the bar
       stays balanced rather than just cropped. */
/* max-height, not height. The logo carries `transition-all duration-500`, and a
   transition declaration outranks even !important in the cascade -- setting
   `height` had no effect at any specificity, unlayered or inside @layer
   utilities, nor as an inline !important. Nothing animates max-height, so it
   lands.

   Both states need a real value. `max-height` defaults to `none`, and
   `none -> 3.1rem` is not interpolatable, so the first attempt jumped straight
   to the end value however long the transition said it was. Giving each
   element its natural height as the expanded value lets it actually travel.
   The link is h-28 (112px) and the image box is h-44 (176px). */
.ph-header a[class*='group/logo'] {
  max-height: 7rem;
}

.ph-header--compact a[class*='group/logo'] {
  max-height: 3.1rem !important;
}

.ph-header [class*='group/logo-svg'] {
  max-height: 11rem;
}

.ph-header--compact [class*='group/logo-svg'] {
  max-height: 3.1rem !important;
}

.ph-header--compact > div {
  padding-top: 0.5rem !important;
  padding-bottom: 0.5rem !important;
}

.ph-header--compact .ph-years-badge img {
  height: 2.1rem;
}

/* the pill and its links tighten a little so the bar reads as one piece */
.ph-header--compact [class*='lg:flex'][class*='flex-1'] [class*='gap-3'] {
  padding: 0.125rem;
}

.ph-header--compact [class*='lg:flex'][class*='flex-1'] a[class*='px-5'] {
  padding-top: 0.35rem;
  padding-bottom: 0.35rem;
}

/* the logo image itself already animates, at 700ms -- bring it into step */
.ph-header--compact [class*='group/logo'] img {
  transition-duration: 400ms;
}

/* 19. Make the shrink glide, and drop the wordmark when compact.
       ---------------------------------------------------------------
       The size change snapped because nothing declared a transition for the
       property being changed. The logo wrapper carries no transition at all,
       and the bar's own `transition-all duration-400` does not reach the
       elements underneath it. Declared here on the elements that actually
       move, in BOTH states, so it eases in and out rather than only one way.

       The wordmark: the shipped logo is a vertical lockup, the monogram at
       y 459-746 of a 1536px artwork with the words below at y 810-1076. The
       mark is 19% of the image height, so no object-fit crop can isolate it at
       a usable size -- js/patches.js lays a purpose-cut images/ph-mark.png over
       the lockup and the two crossfade. Opacity, not a src swap, so nothing
       flashes and nothing is fetched mid-transition. */
.ph-header [class*='group/logo'],
.ph-header [class*='group/logo'] img,
.ph-header .ph-years-badge img {
  transition:
    max-height 420ms cubic-bezier(0.16, 1, 0.3, 1),
    height 420ms cubic-bezier(0.16, 1, 0.3, 1),
    width 420ms cubic-bezier(0.16, 1, 0.3, 1),
    opacity 280ms ease;
}

.ph-header > div {
  transition:
    padding 420ms cubic-bezier(0.16, 1, 0.3, 1),
    background-color 420ms ease;
}

/* the monogram, laid over the lockup in the same box */
.ph-logo-mark {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: contain;
  opacity: 0;
  pointer-events: none;
  transition: opacity 280ms ease;
}

/* !important on both: without it the compact rule lost despite being the more
   specific selector -- the same cascade behaviour that defeated `height`. */
.ph-header--compact .ph-logo-mark {
  opacity: 1 !important;
}

.ph-header--compact [class*='group/logo'] img:not(.ph-logo-mark) {
  opacity: 0 !important;
}

/* Anyone who has asked for less motion gets the smaller header without the
   travel -- the state still changes, it just does not animate. */
@media (prefers-reduced-motion: reduce) {
  .ph-header [class*='group/logo'],
  .ph-header [class*='group/logo'] img,
  .ph-header .ph-years-badge img,
  .ph-header > div,
  .ph-logo-mark {
    transition-duration: 1ms !important;
  }
}
