/* ═══════════════════════════════════════════════════════════════════════
   GLOBAL RESPONSIVE LAYER  ·  loaded by every page in the portfolio
   ───────────────────────────────────────────────────────────────────────
   The pages are Design Components with inline styles, and inline styles
   beat stylesheet rules — so structural overrides here use !important
   deliberately and narrowly.

   RULES OF ENGAGEMENT
   · aspect-ratio is NEVER touched — every media frame keeps its ratio,
     so no image needs reworking. Only the *width* of a frame changes;
     height follows from the ratio automatically.
   · Type hierarchy set per page (clamp()-based headings) is left intact.
   · Standard breakpoints: 1280 / 1024 / 768 / 480.
   ═══════════════════════════════════════════════════════════════════════ */


/* ── 0 · BOX MODEL + OVERFLOW CONTAINMENT ─────────────────────────────
   Several pages never declared a box-sizing reset, so their section
   padding was ADDING to a 100%-wide block — pushing content wider than
   the viewport the moment the fluid gutter grew. border-box everywhere
   makes padding subtract from the declared width instead.             */
*, *::before, *::after {
  box-sizing: border-box;
}

html, body {
  max-width: 100%;
  /* `clip` — NOT `hidden`. overflow-x:hidden on html/body creates a new
     scroll container, which silently breaks `position: sticky` for every
     descendant (it killed the Real-Life Gradients sticky collage).
     `clip` contains the same horizontal overflow without establishing a
     scrollport, so sticky keeps working. */
  overflow-x: clip;
}
img, video, iframe, canvas, svg {
  max-width: 100%;
}
/* The tube-cursor canvas is a fixed full-viewport overlay — exempt it
   so the cap above can't shrink its drawing buffer. */
#tube-cursor-portal, #tube-cursor-portal canvas {
  max-width: none;
}


/* ── 1 · FLUID PAGE GUTTERS ───────────────────────────────────────────
   Replaces every page's fixed 32–48px side padding with one fluid
   gutter: never edge-hugging on a 360px phone, never over-inflated on
   an ultrawide display. :where() keeps specificity at 0 so page-level
   rules can still win where they need to.

   The value lives in --page-gutter so full-bleed elements (e.g. the
   testimonial marquee) can cancel the exact same amount instead of
   hard-coding a number that drifts out of sync.                      */
:root {
  --page-gutter: clamp(20px, 4.5vw, 48px);
}
:where(section, footer, nav) {
  padding-left: var(--page-gutter) !important;
  padding-right: var(--page-gutter) !important;
}

/* Full-bleed rows: cancel exactly the current gutter, at any width. */
[data-marquee-viewport] {
  margin-inline: calc(-1 * var(--page-gutter)) !important;
}


/* ── 2 · FLUID VERTICAL RHYTHM ────────────────────────────────────────
   Section spacing scales with the viewport. The FIRST section on a page
   is the hero — it keeps its own larger top padding so content never
   slides under the fixed nav.                                         */

/* EXEMPTION for scroll-timeline tracks is declared after the breakpoint
   rules below — see the note following section 2. */

@media (max-width: 1024px) {
  section:not(:first-of-type) {
    padding-top: 80px !important;
    padding-bottom: 80px !important;
  }
}
@media (max-width: 768px) {
  section:not(:first-of-type) {
    padding-top: 64px !important;
    padding-bottom: 64px !important;
  }
}
@media (max-width: 480px) {
  section:not(:first-of-type) {
    padding-top: 52px !important;
    padding-bottom: 52px !important;
  }
}


/* EXEMPTION — scroll-timeline tracks. (Declared AFTER the breakpoint
   rules above, and with doubled specificity, so it beats them at every
   width — they're all !important at equal specificity otherwise.)
   A section like the Real-Life Gradients collage is not a content
   section: its height IS the scroll timeline (400vh) and its child is a
   position:sticky stage. Any padding here shifts the sticky stage and
   skews the progress math (track.offsetHeight vs -rect.top), so the
   images reveal at the wrong moment. It stays edge-to-edge and
   padding-free at every breakpoint.                                    */
section[data-collage-track][data-collage-track],
section:has(> [data-collage-stage]):has(> [data-collage-stage]) {
  padding: 0 !important;
}


/* ── 3 · NAV ──────────────────────────────────────────────────────────
   The nav carries a brand lockup on the left and links + theme toggle
   on the right. Shed the least-important parts first as space runs out
   rather than letting it wrap or overflow.                            */
@media (max-width: 1024px) {
  nav { gap: 16px; }
}
@media (max-width: 860px) {
  /* Drop the "— PRODUCT DESIGNER" qualifier before the links go. */
  nav a > span:last-child:not(:first-child) { font-size: 0 !important; }
  nav a > span:last-child:not(:first-child)::after {
    content: ''; display: inline-block;
  }
}
@media (max-width: 640px) {
  nav { padding-top: 12px !important; padding-bottom: 12px !important; }
  /* Generous tap targets without widening the row. */
  nav [data-nav-link] { padding: 10px 6px !important; }
}


/* ── 4 · GRID COLLAPSE SAFETY NET ─────────────────────────────────────
   Pages declare their own collapse rules, but any multi-column grid
   that slipped through gets caught here. Matching on the inline style
   attribute is intentional: these are React style objects, so the
   column template is always serialised into style="".
   auto-fit/auto-fill grids are already fluid and are left alone.      */
@media (max-width: 1024px) {
  [style*="repeat(4,"]:not([style*="auto-fit"]):not([style*="auto-fill"]),
  [style*="repeat(5,"]:not([style*="auto-fit"]):not([style*="auto-fill"]),
  [style*="repeat(6,"]:not([style*="auto-fit"]):not([style*="auto-fill"]) {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }
  [style*="repeat(3,"]:not([style*="auto-fit"]):not([style*="auto-fill"]) {
    grid-template-columns: repeat(2, minmax(0, 1fr)) !important;
  }
}
@media (max-width: 640px) {
  [style*="grid-template-columns"]:not([style*="auto-fit"]):not([style*="auto-fill"]) {
    grid-template-columns: minmax(0, 1fr) !important;
    grid-auto-flow: row !important;
  }
  /* Span declarations from a wider layout would leave holes once the
     grid is a single column. */
  [style*="grid-column"] { grid-column: auto !important; }
  [style*="grid-row"] { grid-row: auto !important; }
}


/* ── 5 · MIN-WIDTH FLOORS ─────────────────────────────────────────────
   flex items with a large flex-basis (cards, rails) must be allowed to
   shrink below it on narrow screens instead of forcing a scrollbar.    */
@media (max-width: 640px) {
  [style*="flex: 1 1 260px"],
  [style*="flex: 1 1 280px"],
  [style*="flex: 1 1 300px"],
  [style*="flex: 0 0 48%"],
  [style*="flex: 0 0 45%"],
  [style*="flex: 0 0 62%"] {
    flex: 1 1 100% !important;
    min-width: 0 !important;
  }
}


/* ── 6 · FIXED-WIDTH MEDIA FRAMES ─────────────────────────────────────
   Frames pinned to an absolute px width become fluid. aspect-ratio is
   untouched, so each frame's height re-derives from its new width and
   the image inside keeps its exact proportions.                        */
@media (max-width: 900px) {
  [data-media-frame][style*="width: 200px"],
  [data-media-frame][style*="width: 220px"],
  [data-media-frame][style*="width: 240px"],
  [data-media-frame][style*="width: 260px"] {
    width: 100% !important;
    max-width: 320px !important;
  }
}


/* ── 7 · FLUID GAPS ───────────────────────────────────────────────────
   Generous desktop gaps (60–112px) read as dead space once the layout
   narrows. Scale the large ones down; small gaps are left alone.      */
@media (max-width: 1024px) {
  [style*="gap: 112px"], [style*="column-gap: 112px"] { gap: 48px !important; }
  [style*="gap: 80px"] { gap: 40px !important; }
  [style*="gap: 60px"] { gap: 36px !important; }
  [style*="gap: 48px"] { gap: 32px !important; }
}
@media (max-width: 640px) {
  [style*="gap: 112px"], [style*="column-gap: 112px"] { gap: 28px !important; }
  [style*="gap: 80px"] { gap: 24px !important; }
  [style*="gap: 60px"] { gap: 22px !important; }
  [style*="gap: 48px"] { gap: 20px !important; }
  [style*="gap: 40px"] { gap: 20px !important; }
}


/* ── 8 · MOBILE TYPE SCALE ────────────────────────────────────────────
   Headings already scale via clamp(). These rules catch the fixed px
   body/lead sizes, stepping each one down by a proportional amount so
   the hierarchy between them survives — a 22px lead stays visibly
   larger than 17px body copy, both just get smaller.                  */
@media (max-width: 640px) {
  [style*="font-size: 24px"] { font-size: 20px !important; }
  [style*="font-size: 22px"] { font-size: 19px !important; }
  [style*="font-size: 20px"] { font-size: 17.5px !important; }
  [style*="font-size: 19px"] { font-size: 17px !important; }
  [style*="font-size: 18px"] { font-size: 16.5px !important; }
  [style*="font-size: 17.5px"] { font-size: 16px !important; }
  [style*="font-size: 17px"] { font-size: 16px !important; }
}
@media (max-width: 400px) {
  [style*="font-size: 24px"] { font-size: 18.5px !important; }
  [style*="font-size: 22px"] { font-size: 18px !important; }
  [style*="font-size: 20px"] { font-size: 16.5px !important; }
  [style*="font-size: 19px"] { font-size: 16px !important; }
  [style*="font-size: 18px"] { font-size: 15.5px !important; }
  [style*="font-size: 17.5px"], [style*="font-size: 17px"] { font-size: 15.5px !important; }
}


/* ── 9 · LONG-WORD / TABLE SAFETY ─────────────────────────────────────
   URLs, mono labels and wide tables are the usual causes of a stray
   horizontal scrollbar on phones.                                      */
@media (max-width: 640px) {
  h1, h2, h3, p, li, td, th, a { overflow-wrap: break-word; }
  table { display: block; overflow-x: auto; }
}


/* ── 10 · ULTRAWIDE ───────────────────────────────────────────────────
   Page wrappers already cap at 1200px. Past ~1800px, open the gutters
   a little further so the capped column doesn't read as a narrow strip
   pinned to a vast empty field.                                        */
@media (min-width: 1800px) {
  :root {
    --page-gutter: clamp(48px, 6vw, 120px);
  }
}


/* ── 11 · REDUCED MOTION ──────────────────────────────────────────────
   Respect the OS setting: kill transitions/animations but keep the
   final visual state (scroll-reveal elements must stay visible).       */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  [data-reveal] { opacity: 1 !important; transform: none !important; }
}
