/* HoneyWired — refreshed homepage styles
   Honors the studio's voice: dotted dividers, Montserrat thin display,
   ink + cream palette, but modernized: cleaner spacing, predictable grid,
   no global horizontal-scroll quirks, no animated gradient body.
   ========================================================================== */

:root {
  /* ===== HoneyWired palette ====================================
     Light mode is paper-cream (#f3f3f4) with deep ink for text.
     Dark mode flips: ink-2 (#373b44) is the elevated surface used
     for cards / footer / nav, with the same paper-cream as primary
     foreground. Accent is the warm pink #e73c7e from our 3-stop
     gradient (#e73c7e \u2192 #23a6d5 \u2192 #23d5ab); use the gradient
     itself only for hero/feature moments via --gradient. */
  --ink: #1a1d24;
  --ink-2: #373b44;
  --cream: #f3f3f4;
  --cream-2: #e6e5ea;
  --rule: #c9c9d1;

  /* 3-stop hero gradient + its individual stops as accent tokens */
  --accent:      #e73c7e;       /* primary accent (pink) */
  --accent-2:    #23a6d5;       /* secondary (blue) */
  --accent-3:    #23d5ab;       /* tertiary (mint) */
  --accent-deep: #b91e5b;       /* deeper pink for hover/pressed */
  --gradient: linear-gradient(135deg, var(--accent) 0%, var(--accent-2) 50%, var(--accent-3) 100%);

  --bg: var(--cream);
  --fg: var(--ink-2);
  --tile-bg: var(--ink-2);
  --tile-fg: var(--cream);
  --font-display: 'Montserrat', 'Helvetica Neue', sans-serif;
  --font-body: 'Source Sans 3', 'Source Sans Pro', 'Helvetica Neue', sans-serif;
  --pad-x: clamp(1.25rem, 5vw, 5rem);
}

html.dark {
  /* Dark mode \u2014 deep near-black page bg with two layers of elevation:
     #1a1d24 for blocks like the about/awards section, #373b44 (the
     slate the user added to the palette) for raised tiles + footer
     accents. Foreground is paper-cream. Accents stay the same as
     light mode \u2014 pink/blue/mint pop equally well on either. */
  --ink: #f3f3f4;
  --ink-2: #e6e5ea;
  --cream: #0e1014;        /* page bg \u2014 deepest layer */
  --cream-2: #1a1d24;       /* elevated section bg (about, etc.) */
  --rule: #2a2e38;
  --bg: var(--cream);
  --fg: var(--ink);
  --tile-bg: #373b44;       /* card / tile surface \u2014 the new slate */
  --tile-fg: #f3f3f4;
}

* { box-sizing: border-box; }
*::after, *::before { box-sizing: border-box; }
html, body { margin: 0; padding: 0; }
html { background: var(--bg); }
body {
  font-family: var(--font-body);
  color: var(--fg);
  background: var(--bg);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  min-height: 100vh;
  transition: background-color .3s ease, color .3s ease;
}

ul, li { list-style: none; padding: 0; margin: 0; }
img { max-width: 100%; display: block; }
a { color: inherit; text-decoration: none; }
button { font: inherit; color: inherit; background: none; border: 0; cursor: pointer; }

/* ============== TOP CHROME ============== */
.site-top {
  position: fixed; top: 0; left: 0; right: 0; z-index: 60;
  display: flex; align-items: center; justify-content: space-between;
  padding: 1.25rem var(--pad-x);
  pointer-events: none;
}
.site-top > * { pointer-events: auto; }

.brand {
  display: flex; align-items: center; gap: .55rem;
  color: var(--ink);
}
.brand .dot { width: .55rem; height: .55rem; border-radius: 50%; background: var(--accent); display: inline-block; }
.brand__wordmark {
  height: 22px; width: auto; display: block;
  /* Explicit filter: invert(0) at rest gives the property something
     animatable when the menu opens. Going from `none` → `invert(1)`
     would snap; going invert(0) → invert(1) interpolates smoothly. */
  filter: invert(0);
  transition: filter .4s ease;
  will-change: filter;
}
html.dark .brand__wordmark { filter: invert(1); }

/* Menu-open: the overlay's background is var(--ink), which is the
   *opposite* color of the page bg in each theme:
     - light theme: --ink is dark  → menu is dark  → logo must be light
     - dark theme:  --ink is cream → menu is cream → logo must be dark
   So the inversion FLIPS when the menu opens, regardless of theme. */
body.menu-open .brand__wordmark { filter: invert(1); }
html.dark body.menu-open .brand__wordmark { filter: invert(0); }

/* hamburger circle button */
.hamburger {
  /* 110:120 honeycomb proportion at compact toggle size. clip-path
     replaces border-radius so the button reads as a hex cell on the
     same visual system as the news thumbs / service indices. */
  width: 55px; height: 60px;
  background: var(--ink); color: var(--cream);
  border-radius: 0;
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  display: flex; align-items: center; justify-content: center;
  position: relative;
  transition: background-color .25s ease;
}
.hamburger:hover { background: var(--accent); color: var(--ink); }
.hamburger__bars { width: 22px; height: 14px; position: relative; }
.hamburger__bars span {
  display: block; position: absolute; left: 0; right: 0; height: 2px;
  background: currentColor; transition: transform .35s cubic-bezier(.7,0,.3,1), opacity .2s ease;
}
.hamburger__bars span:nth-child(1) { top: 0; }
.hamburger__bars span:nth-child(2) { top: 6px; }
.hamburger__bars span:nth-child(3) { top: 12px; }
.is-open .hamburger__bars span:nth-child(1) { transform: translateY(6px) rotate(45deg); }
.is-open .hamburger__bars span:nth-child(2) { opacity: 0; }
.is-open .hamburger__bars span:nth-child(3) { transform: translateY(-6px) rotate(-45deg); }

/* ============== GLOBAL OVERLAY MENU ============== */
.global-menu {
  position: fixed; inset: 0; z-index: 50;
  background: var(--ink);
  color: var(--cream);
  display: flex; flex-direction: column; justify-content: center; align-items: flex-start;
  padding: 6rem var(--pad-x) 3rem;
  /* Reveal radius anchored to the hex toggle's center: 55/2 = 27.5px
     in from the right edge, 60/2 = 30px down from the top of .site-top
     (which sits at 1.25rem padding). */
  clip-path: circle(0% at calc(100% - 27.5px - var(--pad-x)) calc(1.25rem + 30px));
  transition: clip-path .7s cubic-bezier(.77,0,.18,1);
  pointer-events: none;
}
.global-menu.is-open { clip-path: circle(150% at calc(100% - 27.5px - var(--pad-x)) calc(1.25rem + 30px)); pointer-events: auto; }

.global-menu__list { display: flex; flex-direction: column; gap: .35rem; }
.global-menu__item {
  font-family: var(--font-display);
  font-weight: 200;
  font-size: clamp(2.5rem, 8vw, 6rem);
  letter-spacing: -.02em;
  line-height: 1;
  color: var(--cream);
  opacity: 0;
  transform: translateY(30px);
  transition: opacity .4s ease, transform .4s ease, color .2s ease;
  display: inline-flex; align-items: center; gap: 1rem;
}
.global-menu.is-open .global-menu__item { opacity: 1; transform: none; }
.global-menu.is-open .global-menu__item:nth-child(1) { transition-delay: .25s; }
.global-menu.is-open .global-menu__item:nth-child(2) { transition-delay: .32s; }
.global-menu.is-open .global-menu__item:nth-child(3) { transition-delay: .39s; }
.global-menu.is-open .global-menu__item:nth-child(4) { transition-delay: .46s; }
.global-menu.is-open .global-menu__item:nth-child(5) { transition-delay: .53s; }
.global-menu.is-open .global-menu__item:nth-child(6) { transition-delay: .60s; }
.global-menu.is-open .global-menu__item:nth-child(7) { transition-delay: .67s; }
.global-menu__item:hover { color: var(--accent); }
.global-menu__num {
  /* Hex-framed nav index — same 110:120 honeycomb shape used across
     the site, scaled small to sit beside the giant nav labels.
     mask-image draws the thin outline in currentColor. */
  display: inline-flex;
  align-items: center; justify-content: center;
  position: relative;
  width: 1.95em; height: 2.13em; /* 110:120 ratio with breathing room */
  font-family: var(--font-display);
  font-size: .8rem;
  font-weight: 200;
  letter-spacing: 0;
  opacity: .55;
  margin-right: 1rem;
  vertical-align: middle;
}
.global-menu__num::before {
  content: '';
  position: absolute; inset: 0;
  background: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 120'%3E%3Cpolygon points='55,2 108,30 108,90 55,118 2,90 2,30' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 120'%3E%3Cpolygon points='55,2 108,30 108,90 55,118 2,90 2,30' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain;     mask-size: contain;
  -webkit-mask-position: center;  mask-position: center;
  pointer-events: none;
}
.global-menu__foot {
  position: absolute; bottom: 2rem; left: var(--pad-x); right: var(--pad-x);
  display: flex; justify-content: space-between; align-items: center;
  font-size: .75rem; letter-spacing: .15em; text-transform: uppercase; opacity: .55;
}
/* original .global-menu__socials block superseded above; remove duplicate */

/* ============== HERO ============== */
.hero {
  min-height: 100vh;
  padding: 9rem var(--pad-x) 4rem;
  display: grid;
  grid-template-columns: 1fr;
  gap: 2rem;
  align-content: end;
  position: relative;
  border-bottom: 1px dotted var(--rule);
  overflow: hidden;
}
.hero__video {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  z-index: 0;
  opacity: .55;
}
html.dark .hero__video { opacity: .45; }
.hero__video-scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(to bottom, rgba(243,243,244,.5) 0%, rgba(243,243,244,.15) 35%, rgba(243,243,244,.6) 75%, var(--bg) 100%);
  z-index: 1;
}
html.dark .hero__video-scrim {
  background:
    linear-gradient(to bottom, rgba(14,16,20,.5) 0%, rgba(14,16,20,.2) 35%, rgba(14,16,20,.7) 75%, var(--bg) 100%);
}
.hero > *:not(.hero__video):not(.hero__video-scrim) { position: relative; z-index: 2; }
.hero__eyebrow {
  font-size: .75rem; letter-spacing: .3em; text-transform: uppercase;
  display: flex; align-items: center; gap: .75rem;
  font-weight: 600;
}
.hero__eyebrow .pulse {
  width: .55rem; height: .55rem; border-radius: 50%; background: var(--accent);
  box-shadow: 0 0 0 0 currentColor; animation: pulse 2.4s infinite;
}
@keyframes pulse {
  0% { box-shadow: 0 0 0 0 rgba(240, 168, 48, .6); }
  70% { box-shadow: 0 0 0 14px rgba(240, 168, 48, 0); }
  100% { box-shadow: 0 0 0 0 rgba(240, 168, 48, 0); }
}
.hero__title {
  font-family: var(--font-display);
  font-weight: 200;
  font-size: clamp(3rem, 11vw, 11rem);
  line-height: .92;
  letter-spacing: -.025em;
  color: var(--ink);
  margin: 0;
  text-wrap: balance;
}
.hero__title em {
  font-style: normal;
  /* The italicized phrase in the hero gets the full 3-stop gradient
     as a text fill \u2014 our highest-impact use of the brand colors. */
  background: var(--gradient);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  font-weight: 200;
}

.hero__row {
  display: flex; flex-wrap: wrap; gap: 2rem; align-items: end;
  justify-content: space-between;
  margin-top: 1.5rem;
}
.hero__lede {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1rem, 1.4vw, 1.2rem);
  line-height: 1.5;
  max-width: 46ch;
  color: var(--fg);
}
/* Body line inside the hero lede — kill the default browser margin
   on the <p> so it sits cleanly under .about__sticker without an
   extra gap. The sticker's margin-bottom (15px) is the only spacer. */
.hero__lede p { margin: 0; }
.hero__cta {
  display: inline-flex; align-items: center; gap: .75rem;
  font-family: var(--font-display);
  font-weight: 600; text-transform: uppercase; letter-spacing: .15em;
  font-size: .8rem;
  padding: 1rem 1.5rem;
  background: var(--ink); color: var(--cream);
  border-radius: 999px;
  transition: background .25s ease, color .25s ease, transform .15s ease, gap .25s ease;
  align-self: end;
}
.hero__cta:hover { background: var(--accent); color: var(--ink); gap: 1rem; }
.hero__cta:active { transform: translateY(1px); }
.hero__cta .arrow {
  display: inline-flex; align-items: center; justify-content: center;
  font-size: 1rem; line-height: 1;
  transition: transform .25s ease;
}
.hero__cta:hover .arrow { transform: translateX(3px); }

/* hero ticker — sweeping marquee with categories */
.hero__ticker {
  position: absolute; bottom: 0; left: 0; right: 0;
  border-top: 1px dotted var(--rule);
  padding: 1rem 0;
  overflow: hidden;
  display: flex;
  background: var(--bg);
  z-index: 2;
}
.hero__ticker-track {
  display: flex; gap: 3rem; animation: ticker 50s linear infinite;
  white-space: nowrap; padding-right: 3rem;
}
.hero__ticker-item {
  font-family: var(--font-display);
  font-weight: 600; text-transform: uppercase; letter-spacing: .25em;
  font-size: .8rem;
  display: inline-flex; align-items: center; gap: 1rem;
  color: var(--ink);
}
.hero__ticker-item .sep { color: var(--accent); }
@keyframes ticker {
  from { transform: translateX(0); }
  to { transform: translateX(-50%); }
}

/* ============== WORK SECTION ============== */
.work {
  padding: 6rem var(--pad-x) 4rem;
}
.section-head {
  display: flex; align-items: end; justify-content: space-between;
  margin-bottom: 3rem; gap: 2rem; flex-wrap: wrap;
}
.section-head__title {
  font-family: var(--font-display);
  font-weight: 200;
  font-size: clamp(2rem, 4vw, 3.5rem);
  letter-spacing: -.015em;
  margin: 0; color: var(--ink);
  line-height: 1;
}
.section-head__title .num {
  /* Hexagon-framed index — same dimensions/sizing as the subpage
     eyebrow hex so both pages share one badge scale. Fixed in rem
     (not em-relative to the heading) so it doesn't balloon on
     ultrawide where the title clamps up to 3.5rem. */
  display: inline-flex;
  align-items: center; justify-content: center;
  position: relative;
  width: 2.2em; height: 2.4em; /* 110:120 ratio — same as eyebrow */
  font-family: var(--font-display);
  font-size: .8rem;
  vertical-align: middle; letter-spacing: 0;
  margin-right: .65rem;
  font-weight: 200;
  color: var(--ink); opacity: .55;
}
.section-head__title .num::before {
  content: '';
  position: absolute; inset: 0;
  background: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 120'%3E%3Cpolygon points='55,2 108,30 108,90 55,118 2,90 2,30' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 120'%3E%3Cpolygon points='55,2 108,30 108,90 55,118 2,90 2,30' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain;     mask-size: contain;
  -webkit-mask-position: center;  mask-position: center;
  pointer-events: none;
}
.filter-bar { display: flex; flex-wrap: wrap; gap: .25rem; }
.filter-btn {
  font-family: var(--font-display);
  font-weight: 600; text-transform: uppercase;
  letter-spacing: .15em; font-size: .72rem;
  padding: .65rem 1rem;
  border-radius: 999px;
  color: var(--fg); opacity: .6;
  transition: all .2s ease;
  display: inline-flex; align-items: center; gap: .5rem;
}
/* Color-coded hex before each category label. Inherits from
   --cat-color set via [data-cat] (see CATEGORY COLORS below).
   The "All" filter omits its hex since it has no single color.
   110:120 ratio — same honeycomb shape used everywhere on the site. */
.filter-btn[data-cat]:not([data-cat="all"])::before {
  content: '';
  width: .55rem; height: .6rem;
  background: var(--cat-color, var(--accent));
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  flex: 0 0 auto;
  transition: transform .2s ease;
}
.filter-btn:hover { opacity: 1; }
.filter-btn:hover::before { transform: scale(1.15); }
.filter-btn.is-active { background: var(--ink); color: var(--cream); opacity: 1; }
.filter-btn .count {
  font-family: var(--font-body); font-weight: 400;
  font-size: .65rem; opacity: .55; margin-left: .35rem;
}

/* ============== CATEGORY COLORS ==============
   Per-category accent dots applied to both the filter buttons and
   the meta strip on each tile. Categories that don\u2019t match one of
   these IDs fall back to --accent. */
[data-cat="experiential"] { --cat-color: #23d5ab; }   /* mint  */
[data-cat="film"]         { --cat-color: #23a6d5; }   /* blue  */
[data-cat="social"]       { --cat-color: #e73c7e; }   /* pink  */
[data-cat="digital"]      { --cat-color: #8b5cf6; }   /* violet */

/* GRID
   Lock aspect-ratio on the BASE tile only (1:1). Size variants drop
   aspect-ratio and instead span multiple grid tracks — because grid-auto-rows
   matches the column width and the tracks include gaps, a 2-col tile is
   exactly 2× col + 1 gap wide and a 2-row tile is exactly 2× row + 1 gap tall.
   That math makes wide / tall / huge tiles align perfectly with neighbouring
   1×1 tiles, no fractional slivers. */
.work-grid {
  display: grid;
  gap: 1.5rem;
  grid-template-columns: repeat(2, 1fr);
  grid-auto-rows: 1fr;
  grid-auto-flow: dense;
}
@media (min-width: 720px)  { .work-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1100px) { .work-grid { grid-template-columns: repeat(4, 1fr); } }

.tile {
  position: relative;
  aspect-ratio: 1 / 1;
  border-radius: 6px;
  cursor: pointer;
  background: var(--tile-bg);
  color: var(--tile-fg);
  transition: transform .35s cubic-bezier(.2,.8,.2,1), box-shadow .35s ease;
  will-change: transform;
  z-index: 1;
  /* Mount-time slide-up cascade. Per VanillaTilt's compositing model
     we deliberately animate ONLY translateY here, not opacity:
     fading the parent down hides the image fade-in (which has its own
     keyframe), making the image look like it pops in at the end. By
     keeping the tile visible throughout and only sliding it, the
     image's .55s fade plays in parallel and is actually perceptible.
     `backwards` fill holds the from-state during the per-tile delay
     so cascading tiles don't flash visible early; once the animation
     finishes there's no fill, leaving transform free for VanillaTilt
     to drive the tilt effect. --i is set per-tile in JSX; min() caps
     at 12 so deep-grid tiles don't wait forever. */
  animation: hw-tile-rise .7s cubic-bezier(.2,.8,.2,1) backwards;
  animation-delay: calc(min(var(--i, 0), 12) * 50ms);
}
@keyframes hw-tile-rise {
  from { transform: translateY(28px); }
  to   { transform: translateY(0); }
}
/* The tile ROOT does NOT clip \u2014 so 3D-popped text layers can poke past
   the rounded corners during tilt without being chopped flat. The image,
   gradient, and our custom glare all live inside .tile__bg, which clips
   them to the rounded shape. (We disabled VanillaTilt\u2019s built-in glare
   in JS because that one attaches to the tile root and would escape.) */
.tile__bg {
  position: absolute; inset: 0;
  border-radius: inherit;
  overflow: hidden;
  z-index: 0;
}
/* Bottom-fade overlay for caption legibility. Lives on .tile__bg so it
   inherits the rounded clip AND sits at z=0 (no translateZ), so it
   never inflates via perspective during tilt. Applied to every tile,
   not just image-backed ones, so caption text reads cleanly even on
   gradient fallbacks. */
.tile__bg::before {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0;
  height: 65%;
  background: linear-gradient(
    to top,
    rgba(0,0,0,.85) 0%,
    rgba(0,0,0,.6) 25%,
    rgba(0,0,0,.25) 55%,
    rgba(0,0,0,0) 100%
  );
  pointer-events: none;
  z-index: 2;
}
.tile.has-img .tile__bg::after {
  content: ''; position: absolute; inset: 0;
  background: linear-gradient(to top, rgba(0,0,0,.15) 0%, rgba(0,0,0,0) 35%);
  pointer-events: none;
  z-index: 2;
}
/* hovered, focused, or actively-tilted tiles must paint above neighbours
   so the depth illusion isn\u2019t broken by adjacent tiles clipping the lift */
.tile:hover,
.tile:focus-visible,
.tile.is-tilt:hover { z-index: 5; }
/* legacy is-feature alias — wide variant (2 cols) */
.tile.is-feature:not([class*="is-size-"]) { grid-column: span 2; aspect-ratio: auto; }
/* size variants — drop aspect-ratio; spanned tracks (which include gaps)
   determine the size, so neighbours align. */
.tile.is-size-wide { grid-column: span 2; grid-row: span 1; aspect-ratio: auto; }
.tile.is-size-tall { grid-column: span 1; grid-row: span 2; aspect-ratio: auto; }
.tile.is-size-huge { grid-column: span 2; grid-row: span 2; aspect-ratio: auto; }

.tile__bg-grad {
  position: absolute; inset: 0;
  background: linear-gradient(135deg, var(--c1, #2a2e38), var(--c2, #14161b));
  transition: transform .6s cubic-bezier(.2,.8,.2,1);
}
.tile:hover .tile__bg-grad { transform: scale(1.06); }

.tile__img {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  object-fit: cover;
  opacity: 0;
  /* Plain transitions on opacity + transform — turned out to be more
     reliable across browsers' image-caching paths than a keyframed
     animation (some browsers were skipping the keyframe when React
     committed the .is-loaded class in the same paint as mount). The
     React-side double-rAF in Tile() guarantees a paint at opacity 0
     before flipping the class, so the transition reliably interpolates. */
  transition: opacity .55s ease, transform .6s cubic-bezier(.2,.8,.2,1);
  z-index: 1;
}
.tile__img.is-loaded { opacity: 1; }
.tile:hover .tile__img.is-loaded { transform: scale(1.06); }

.tile__meta {
  position: absolute; left: 0; right: 0; top: 0;
  padding: 1.1rem 1.2rem;
  display: flex; justify-content: space-between; align-items: center;
  gap: .75rem;
  font-size: .65rem; letter-spacing: .25em; text-transform: uppercase;
  color: rgba(243,243,244,.7);
  font-family: var(--font-display); font-weight: 600;
  z-index: 2;
  min-width: 0;
}
.tile__meta .cat,
.tile__meta .year { min-width: 0; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.tile__meta .cat { display: inline-flex; align-items: center; gap: .4rem; }
.tile__meta .cat::before {
  /* Tiny honeycomb cell instead of a circular dot — 110:120 ratio at
     small size, clipped via polygon. Same shape system as the section
     index hex, news thumbnails, hamburger, and cursor follower. */
  content: '';
  width: .55rem; height: .6rem;
  background: var(--cat-color, var(--accent));
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
}
/* Hover: pulse the dot between its category color and a deep ink so
   each tile gets a subtle "live" indicator. .85s round-trip — fast
   enough to feel attentive, slow enough not to feel anxious. The
   keyframes shift the actual `background`, not opacity, so the dot
   stays solid (not faded) at both ends of the cycle. */
@keyframes hw-cat-blink {
  0%, 100% { background: var(--cat-color, var(--accent)); }
  50%      { background: var(--ink); }
}
.tile:hover .tile__meta .cat::before {
  animation: hw-cat-blink .85s ease-in-out infinite;
}
html.dark .tile:hover .tile__meta .cat::before {
  /* In dark mode --ink is the page bg, which would make the "off"
     state invisible. Swap to a lighter dark so the blink reads. */
  animation-name: hw-cat-blink-dark;
}
@keyframes hw-cat-blink-dark {
  0%, 100% { background: var(--cat-color, var(--accent)); }
  50%      { background: #2a2e38; }
}
.tile__meta .year { font-variant-numeric: tabular-nums; opacity: .55; }



/* corner index badge */
.tile__index {
  position: absolute; top: 1rem; right: 1.1rem;
  font-family: var(--font-display); font-weight: 800;
  font-size: clamp(.7rem, 1.2vw, .9rem);
  letter-spacing: .15em;
  color: #f3f3f4;
  background: rgba(0,0,0,.35);
  border: 1px solid rgba(255,255,255,.15);
  backdrop-filter: blur(8px);
  padding: .4rem .55rem;
  border-radius: 999px;
  z-index: 4;
  display: none; /* hide by default; meta strip carries the year. Re-enable on tilt for parallax pop */
}
.tile.is-tilt .tile__index { display: inline-flex; }
/* hide year on tilt mode since the index badge takes that corner */

/* VanillaTilt\u2019s built-in glare attaches as `.js-tilt-glare` directly
   inside the tile root. We keep the tile root overflow:visible so that
   3D-popped text layers (translateZ during tilt) can show past the
   rounded corners. To prevent the glare from leaking with them, we
   clip the glare element itself with border-radius + overflow:hidden.
   The lib already sets overflow:hidden internally; this just enforces
   the rounded shape and z-stacking. */
.tile .js-tilt-glare {
  border-radius: inherit;
  overflow: hidden;
  z-index: 1 !important;
  pointer-events: none;
}

.tile__caption {
  position: absolute; left: 0; right: 0; bottom: 0;
  padding: 1.25rem 1.2rem 1.1rem;
  z-index: 2;
  display: flex; flex-direction: column; gap: .3rem;
  /* No background here \u2014 the dark gradient that fades the bottom of
     the image lives on .tile__bg::before so it stays clipped to the
     image shape. If we put it here, the caption itself gets translateZ\u2019d
     during tilt, which inflates the gradient via perspective projection
     and bleeds it past the tile edge. */
  min-width: 0;
}
.tile__caption > * { min-width: 0; }
.tile__sub {
  font-family: var(--font-body);
  font-size: .8rem; line-height: 1.3;
  color: rgba(243,243,244,.7);
  margin-top: .15rem;
}
.tile__client {
  font-size: .65rem; letter-spacing: .25em; text-transform: uppercase;
  font-family: var(--font-display); font-weight: 600;
  color: rgba(243,243,244,.7);
}
.tile__title {
  font-family: var(--font-display);
  font-weight: 300;
  font-size: clamp(1rem, 1.5vw, 1.4rem);
  line-height: 1.15;
  color: #f3f3f4;
  letter-spacing: -.005em;
  text-wrap: balance;
}
.tile.is-feature .tile__title {
  font-size: clamp(1.6rem, 2.6vw, 2.4rem);
}
/* Bump titles on size variants so they fill the extra real estate.
   Wide / huge tiles get the most generous treatment; tall tiles match
   feature size since width is the same. */
.tile.is-size-wide .tile__title,
.tile.is-size-huge .tile__title { font-size: clamp(1.7rem, 2.8vw, 2.6rem); }
.tile.is-size-tall .tile__title { font-size: clamp(1.4rem, 2.1vw, 2rem); }
.tile.is-size-huge .tile__title { font-size: clamp(2rem, 3.4vw, 3.2rem); }

/* Increase caption padding on bigger tiles so text breathes */
.tile.is-size-wide .tile__caption,
.tile.is-size-tall .tile__caption { padding: 1.75rem 1.6rem 1.5rem; }
.tile.is-size-huge .tile__caption { padding: 2.25rem 2rem 1.9rem; }

/* Top meta strip needs more breathing room on bigger tiles too */
.tile.is-size-wide .tile__meta,
.tile.is-size-tall .tile__meta { padding: 1.5rem 1.6rem; }
.tile.is-size-huge .tile__meta { padding: 1.9rem 2rem; }

/* Mobile: tiles compress to one or two columns, so the desktop padding
   eats into the headline. Pull the caption + meta padding down so the
   client/title/cat have room to breathe at narrow widths. */
@media (max-width: 640px) {
  .tile__meta,
  .tile.is-size-wide .tile__meta,
  .tile.is-size-tall .tile__meta,
  .tile.is-size-huge .tile__meta { padding: .75rem .85rem; }
  .tile__caption,
  .tile.is-size-wide .tile__caption,
  .tile.is-size-tall .tile__caption,
  .tile.is-size-huge .tile__caption { padding: .85rem .85rem .8rem; }
}

/* hover variants — controlled by data-hover on .work-grid */
[data-hover="overlay"] .tile::after {
  content: ''; position: absolute; inset: 0;
  background: var(--accent);
  mix-blend-mode: multiply;
  opacity: 0;
  transition: opacity .25s ease;
  z-index: 3; pointer-events: none;
}
[data-hover="overlay"] .tile:hover::after { opacity: .7; }

[data-hover="zoom"] .tile:hover .tile__bg-grad { transform: scale(1.12); }

/* ============== TILT PARALLAX
   Only apply translateZ on hover, so layers pop forward as the user
   rotates the tile and return flush at rest. Static translateZ would
   inflate caption widths via perspective projection (a layer at z=72px
   with perspective=1000px is rendered ~1.08× larger), causing captions
   to overflow size-variant tiles even at rest. Keeping the effect tied
   to :hover keeps the layout pixel-perfect when not interacting. */
[data-hover="tilt"] .tile { transform-style: preserve-3d; perspective: 1000px; }
.tile.is-tilt { transform-style: preserve-3d; }
.tile.is-tilt .tile__layer {
  transform-style: preserve-3d;
  will-change: transform;
  transform: translateZ(0);
  transition: transform .35s cubic-bezier(.2,.8,.2,1);
}
.tile.is-tilt:hover .tile__layer[data-depth="1"] { transform: translateZ(16px); }
.tile.is-tilt:hover .tile__layer[data-depth="2"] { transform: translateZ(32px); }
.tile.is-tilt:hover .tile__layer[data-depth="3"] { transform: translateZ(50px); }
.tile.is-tilt:hover .tile__layer[data-depth="4"] { transform: translateZ(70px); }

/* News (text-only) tiles use a much smaller pop. The headline fills
   most of the tile, so even modest perspective inflation pushes letters
   visibly out of bounds. Halve every depth here so the parallax is
   felt as a subtle layered shift, not a typography blow-up. */
.tile.is-news.is-tilt:hover .tile__layer[data-depth="1"] { transform: translateZ(6px); }
.tile.is-news.is-tilt:hover .tile__layer[data-depth="2"] { transform: translateZ(12px); }
.tile.is-news.is-tilt:hover .tile__layer[data-depth="3"] { transform: translateZ(18px); }
.tile.is-news.is-tilt:hover .tile__layer[data-depth="4"] { transform: translateZ(24px); }

[data-hover="lift"] .tile:hover { transform: translateY(-8px); box-shadow: 0 20px 40px rgba(0,0,0,.18); }

[data-hover="none"] .tile:hover { transform: none; }
[data-hover="none"] .tile:hover .tile__bg-grad { transform: none; }

/* module / stat tile (the parallax stat card from original CSS) */
.tile.is-module .tile__bg-grad { background: var(--ink); }
.tile.is-module .module {
  position: absolute; inset: 0; padding: 9%;
  display: flex; flex-direction: column; justify-content: space-between;
  z-index: 2;
}
.tile.is-module .module__eyebrow,
.tile.is-module .module__cta {
  font-family: var(--font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .15em;
  font-size: clamp(.65rem, 1.1vw, .8rem);
  color: var(--cream); opacity: .65;
}
.tile.is-module .module__stats {
  display: flex; flex-direction: column; gap: .5rem;
  margin: auto 0;
}
.tile.is-module .module__stat {
  display: flex; align-items: baseline; gap: .55rem; line-height: 1;
}
.tile.is-module .module__num {
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(2rem, 4.6vw, 3.4rem); letter-spacing: -.02em;
  color: var(--cream);
}
.tile.is-module .module__label {
  font-family: var(--font-body); font-size: clamp(.8rem, 1.3vw, 1rem);
  color: var(--cream); opacity: .55; text-transform: lowercase;
}

/* ============== NEWS TILE ============== */
/* News tiles slot into the work grid as bite-sized callouts:
   bold accent backgrounds, no photo, lots of breathing room */
.tile.is-news {
  background: var(--accent);
  color: var(--ink);
  display: flex;
  /* News tiles opt INTO clipping (the base .tile leaves overflow visible
     so 3D-popped layers can poke past during tilt). The oversized
     ornament glyph at the bottom-right needs to be cut off at the tile
     edge instead of bleeding into the next grid cell. */
  overflow: hidden;
}
.tile.is-news .tile__bg,
.tile.is-news.has-img::before { display: none; }
.tile.is-news.is-kind-press { background: var(--accent-2); color: #fff; }
.tile.is-news.is-kind-award { background: var(--accent); color: #fff; }
/* "We're Hiring" callout — always high-contrast cream-on-ink with a thick
   accent rule on the left edge to make it feel like a posted notice */
.tile.is-news.is-kind-note  {
  background: var(--ink-2);
  color: var(--cream);
  box-shadow: inset 6px 0 0 var(--accent);
}
html.dark .tile.is-news.is-kind-note { background: #0a0c10; }

.tile.is-news .news {
  position: absolute; inset: 0;
  padding: 8% 8% 7%;
  display: flex; flex-direction: column; justify-content: space-between;
  z-index: 2;
}
/* Mobile: tiles compress to single-column width, so 8% percentage
   padding turns into too much absolute padding for the now-shorter
   content. Pull it down so the headline isn't crowding the tag/date
   row above and the source/cta row below. */
@media (max-width: 640px) {
  .tile.is-news .news { padding: 1.1rem 1.1rem .9rem; }
  /* Default tile sits at half-screen width on mobile (work-grid is
     2 columns by default), so its news copy needs to be much smaller
     than the full-width wide/huge variants. Setting the small sizes
     here, then bumping the wide/huge variants back up. */
  .tile.is-news .news__top  { font-size: .26rem; }
  .tile.is-news .news__bot  { font-size: .28rem; }
  .tile.is-news .news__head { font-size: clamp(.3rem, 1.5vw, .45rem); }
  /* Tighter padding for the half-width tile too — 1.1rem leaves no
     room for the headline at this size. */
  .tile.is-news:not(.is-size-wide):not(.is-size-tall):not(.is-size-huge) .news {
    padding: .75rem .8rem .65rem;
  }
  /* Hide the source line on half-width tiles — "Cannes Lions" /
     "Adweek" doubles up with the project title above. Keep the kind
     tag (LAUNCH / RECOGNITION / AWARD) — it carries useful color and
     context. With source hidden, the bottom row only has the CTA, so
     push it right via margin-left: auto. */
  .tile.is-news:not(.is-size-wide):not(.is-size-tall):not(.is-size-huge) .news__source {
    display: none;
  }
  .tile.is-news:not(.is-size-wide):not(.is-size-tall):not(.is-size-huge) .news__cta {
    margin-left: auto;
  }
  /* Wide/huge span full width on mobile — keep their type generous. */
  .tile.is-size-wide.is-news .news__top,
  .tile.is-size-huge.is-news .news__top { font-size: .58rem; }
  .tile.is-size-wide.is-news .news__bot,
  .tile.is-size-huge.is-news .news__bot { font-size: .6rem; }
  .tile.is-size-wide.is-news .news__head,
  .tile.is-size-huge.is-news .news__head {
    font-size: clamp(.95rem, 4.2vw, 1.4rem);
  }
}
.tile.is-news .news__top {
  display: flex; align-items: center; justify-content: space-between;
  font-family: var(--font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em;
  font-size: .68rem;
}
.tile.is-news .news__tag {
  display: inline-flex; align-items: center; gap: .45em;
  padding: .35em .6em; border-radius: 999px;
  background: rgba(0,0,0,.12); color: inherit;
}
.tile.is-news.is-kind-press .news__tag,
.tile.is-news.is-kind-note  .news__tag { background: rgba(255,255,255,.12); }
.tile.is-news.is-kind-note  .news__tag { color: var(--accent); }
.tile.is-news .news__tag::before {
  content: ''; width: .42em; height: .42em; border-radius: 50%;
  background: currentColor; opacity: .9;
}
.tile.is-news .news__date { opacity: .65; }

.tile.is-news .news__head {
  font-family: var(--font-display); font-weight: 700;
  font-size: clamp(1.05rem, 1.7vw, 1.55rem);
  line-height: 1.15; letter-spacing: -.01em;
  text-wrap: balance;
  margin: .5rem 0 0;
}
.tile.is-size-wide.is-news .news__head,
.tile.is-size-huge.is-news .news__head {
  font-size: clamp(1.5rem, 2.4vw, 2.4rem);
  max-width: 22ch;
}

.tile.is-news .news__bot {
  display: flex; align-items: center; justify-content: space-between;
  font-family: var(--font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .14em;
  font-size: .7rem;
}
.tile.is-news .news__source { opacity: .8; }
.tile.is-news .news__cta { display: inline-flex; gap: .4em; align-items: center; }
.tile.is-news .news__cta .arrow {
  display: inline-block; transition: transform .25s ease;
}
.tile.is-news:hover .news__cta .arrow { transform: translateX(4px); }

/* ornament: oversized quote / asterisk for visual texture */
.tile.is-news .news__ornament {
  position: absolute; right: -.05em; bottom: -.18em;
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(8rem, 18vw, 18rem);
  line-height: .8; letter-spacing: -.05em;
  color: currentColor; opacity: .08;
  pointer-events: none; z-index: 1;
  user-select: none;
}

/* ============== BRANDS ============== */
.brands {
  padding: 5rem var(--pad-x);
  border-top: 1px dotted var(--rule);
  border-bottom: 1px dotted var(--rule);
}
.brand-grid {
  display: grid;
  /* Uniform 6-column grid \u2014 every cell is the same size, gap is
     generous so logos breathe. Logos themselves are scaled inside
     each cell (in BrandCell) so they appear at consistent optical
     weight despite their individual aspect ratios. */
  grid-template-columns: repeat(12, minmax(0, 1fr));
  gap: 1.25rem;
  margin-top: 2.5rem;
}
@media (max-width: 1100px) {
  .brand-grid { grid-template-columns: repeat(8, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
  .brand-grid { grid-template-columns: repeat(6, minmax(0, 1fr)); gap: .75rem; }
}
.brand-cell {
  /* Every cell is the same fixed shape; logos inside are sized in JS
     so they carry roughly equal optical weight regardless of their
     individual aspect ratio. Cell ratio of 1.6 \u2014 matches the
     CELL_RATIO constant used in BrandCell. Adjust both together. */
  aspect-ratio: 1.6 / 1;
  position: relative;
  display: flex; align-items: center; justify-content: center;
  padding: 0;
  border-radius: 6px;
  background: transparent;
  overflow: hidden;
}
.brand-cell img {
  /* Width and height are set inline by JS based on the logo\u2019s aspect
     ratio + a target visual area. The brightness(0) silhouettes the
     logo to a single color; opacity then dims that to a quiet grey. */
  object-fit: contain;
  opacity: .35;
  transition: opacity .25s ease;
  filter: brightness(0);
}
/* In dark mode the silhouette flips to white, then dims via opacity
   for the same quiet-grey effect against the dark page bg. */
html.dark .brand-cell img { filter: brightness(0) invert(1); opacity: .5; }
/* Hover: just lift the opacity. No background swap, no transform. */
.brand-cell:hover img        { opacity: .9; }
html.dark .brand-cell:hover img { opacity: 1; }

/* ============== AWARDS ============== */
.awards {
  padding: 5rem var(--pad-x);
}
.awards__list {
  display: grid; grid-template-columns: 1fr;
  margin-top: 2.5rem;
  border-top: 1px dotted var(--rule);
}
@media (min-width: 800px) { .awards__list { grid-template-columns: 1fr 1fr; column-gap: 4rem; } }
.award-row {
  display: flex; align-items: center; gap: 1rem;
  padding: 1.1rem 0;
  border-bottom: 1px dotted var(--rule);
  font-family: var(--font-display);
}
.award-row__name {
  font-weight: 300;
  font-size: clamp(1.1rem, 1.6vw, 1.35rem);
  color: var(--ink);
  letter-spacing: -.005em;
}
.award-row__rule { flex: 1; }
/* Hardware icon strip \u2014 right-aligned row of small award marks,
   color-coded by tier. Each icon is rendered as an inline SVG (fetched
   once, with all fills rewritten to currentColor) so we can recolor
   it via CSS `color`. PNGs fall back to a multiply-blend tint. */
.award-row__hw {
  display: inline-flex; align-items: center; gap: .35rem;
  flex-wrap: wrap; justify-content: flex-end;
  flex: 0 1 auto;
  max-width: 70%;
  row-gap: .35rem;
}
.award-hw {
  display: inline-flex; align-items: center; justify-content: center;
  height: 22px; width: 56px;
  color: var(--tier-color);
  opacity: .9;
  transition: opacity .2s ease, transform .2s ease, color .2s ease;
  cursor: default;
  flex: 0 0 auto;
  overflow: hidden;
}
.award-hw > svg { width: 100%; height: 100%; display: block; }
.award-hw[data-fallback] > img {
  width: 100%; height: 100%; object-fit: contain;
  /* Tint the PNG (Muse marks) toward the tier color. brightness(0)
     knocks the original to black; the background bleed via mix-blend
     would require a known surrounding bg, so we use a tinted overlay
     via filter chain that approximates the tier hue. Not as clean as
     the inline SVG path, but readable. */
  filter: brightness(0) saturate(100%);
  /* Simulate tier color by stacking sepia + hue-rotate; tuned per
     tier in [data-tier] rules below. */
}
.award-hw[data-tier="gold"]   { --tier-color: #d4a437; }   /* warm gold */
.award-hw[data-tier="silver"] { --tier-color: #b8bcc4; }   /* cool silver */
.award-hw[data-tier="bronze"] { --tier-color: #b87333; }   /* copper bronze */
.award-hw[data-tier="merit"]  { --tier-color: #8e919a; }   /* slightly darker than silver */
/* One Show and P&G President's Award marks read large; keep them at the
   original compact size so they don't dominate their rows. */
.award-row[data-mark="oneshow"] .award-hw,
.award-row[data-mark="pg"] .award-hw { height: 16px; width: 42px; }
html.dark .award-hw[data-tier="merit"] { --tier-color: #6a6e78; }
.award-hw:hover {
  opacity: 1;
  transform: translateY(-1px) scale(1.1);
}

/* Floating tooltip rendered by <Awards/>. Pinned in viewport coords
   from the icon\u2019s bounding rect (top-center) and translated up so
   the tail clears the icon. */
.award-tip {
  position: fixed; z-index: 200;
  transform: translate(-50%, -100%);
  background: var(--ink); color: var(--cream);
  font-family: var(--font-body);
  font-size: .72rem; line-height: 1.35;
  padding: .55rem .75rem;
  border-radius: 6px;
  max-width: 320px;
  box-shadow: 0 8px 24px rgba(0,0,0,.18);
  pointer-events: none;
  white-space: normal;
  text-align: center;
}
html.dark .award-tip { background: #f3f3f4; color: #1a1d24; }
.award-cell {
  display: flex; flex-direction: column; align-items: center; text-align: center;
  gap: .5rem;
}
.award-cell__mark {
  width: 88px; height: 88px;
  display: flex; align-items: center; justify-content: center;
  position: relative;
  padding: 1rem;
}
.award-cell__mark img {
  max-width: 100%; max-height: 100%;
  width: auto; height: auto; object-fit: contain;
  filter: brightness(0);
  opacity: .85;
}
html.dark .award-cell__mark img { filter: brightness(0) invert(1); }
.award-cell__mark.is-gold::after {
  content: ''; position: absolute; inset: 0; border-radius: 50%;
  background: radial-gradient(circle at 50% 40%, var(--accent) 0%, transparent 65%);
  opacity: .35; z-index: -1;
}
.award-cell__sub {
  font-family: var(--font-display); font-weight: 600;
  font-size: .65rem; letter-spacing: .25em; text-transform: uppercase;
  opacity: .65;
}
.award-cell__name {
  font-family: var(--font-body); font-weight: 600; font-size: .9rem;
  color: var(--ink);
}
.award-cell__count {
  font-family: var(--font-display); font-weight: 200;
  font-size: 2rem; line-height: 1; color: var(--ink);
}

/* ============== CALLOUT (services / news) ==============
   Sticker-grid layout: oversized display headline + button on the left,
   long-form body copy on the right. Three `tone` variants give the
   homepage an alternating-band rhythm as you scroll. The CTA sits
   directly under the headline so the entire left column reads as one
   group. */
.callout {
  padding: 6rem var(--pad-x);
}
.callout__grid {
  display: grid; gap: 3rem;
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 900px) {
  .callout__grid { grid-template-columns: 1fr 1.6fr; gap: 5rem; }
}
.callout__head {
  display: flex; flex-direction: column;
  gap: 2rem;
  align-items: flex-start;
}
.callout__sticker {
  font-family: var(--font-display);
  font-weight: 200;
  /* Tier 3 — homepage callout modules sit below the hero (5.35rem max)
     and below subpage titles (4.75rem max). */
  font-size: clamp(2rem, 4.5vw, 4rem);
  line-height: .95; letter-spacing: -.02em;
}
.callout__sticker em {
  font-style: normal; font-weight: 200;
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
.callout__copy {
  font-family: var(--font-body); font-weight: 300;
  font-size: 1.05rem; line-height: 1.65;
  /* Flex column so .hero__cta's existing `align-self: end` resolves
     to the right edge of the column. Paragraphs stay full-width via
     the default `align-self: stretch`. */
  display: flex;
  flex-direction: column;
}
.callout__copy p { margin: 0 0 1.25rem; }
.callout__copy p:last-of-type { margin-bottom: 0; }
.callout__copy .hero__cta { margin-top: 2rem; }

/* Tone variants — fixed colors so the alternating rhythm reads the
   same in either theme. The dark/mid pair gives "lighter dark" contrast
   without flipping with theme; light is the cream band. */
.callout--dark { background: #14161b; color: #c1c4cc; }
.callout--dark .callout__sticker { color: #f3f3f4; }

.callout--mid  { background: #2a2e38; color: #c8cad2; }
.callout--mid .callout__sticker { color: #f3f3f4; }

.callout--light { background: var(--cream-2); color: var(--fg); }
.callout--light .callout__sticker { color: var(--ink); }

/* Buttons invert on dark/mid: light pill with dark ink so the CTA pops
   off the darker band. Hover brings the brand accent in. */
.callout--dark .hero__cta,
.callout--mid  .hero__cta {
  background: #f3f3f4;
  color: #1a1d24;
}
.callout--dark .hero__cta:hover,
.callout--mid  .hero__cta:hover {
  background: var(--accent);
  color: #1a1d24;
}

/* Strong/em inside the body copy — make sure they read against the
   variant's text color rather than always defaulting to --ink. */
.callout--dark .callout__copy strong,
.callout--mid  .callout__copy strong { color: #f3f3f4; }
.callout--light .callout__copy strong { color: var(--ink); }

/* ============== ABOUT ============== */
.about {
  padding: 6rem var(--pad-x);
  border-top: 1px dotted var(--rule);
  background: var(--cream-2);
}
html.dark .about { background: #14161b; }
.about__grid {
  display: grid; gap: 3rem;
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 900px) {
  .about__grid { grid-template-columns: 1fr 1.6fr; gap: 5rem; }
  /* About subpage opts out of the 2-column treatment — the long-form
     copy reads better as one column at the page padding edge. */
  .about__grid.is-single { grid-template-columns: 1fr; gap: 3rem; }
}
.about__sticker {
  font-family: var(--font-display);
  font-weight: 200;
  font-size: clamp(2.5rem, 5vw, 4rem);
  line-height: .95; letter-spacing: -.02em;
  color: var(--ink);
}
/* Hero sticker — same display-font treatment as .about__sticker but
   scoped to the hero so the body line below it (which sits in .hero__lede
   using --font-body) doesn't inherit the display font. */
.hero__sticker {
  font-family: var(--font-display);
  font-weight: 200;
  font-size: clamp(2.5rem, 5.8vw, 5.35rem);
  line-height: .95; letter-spacing: -.02em;
  color: var(--ink);
  margin-bottom: 15px;
}
.hero__sticker em {
  font-style: normal; font-weight: 200;
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
}
.about__sticker em {
  font-style: normal;
  /* Match the hero's gradient text treatment so the brand-color
     moments echo through the page. */
  background: var(--gradient);
  -webkit-background-clip: text;
          background-clip: text;
  -webkit-text-fill-color: transparent;
  color: transparent;
  font-weight: 200;
}
.about__copy p {
  font-family: var(--font-body);
  font-weight: 300; font-size: 1.05rem; line-height: 1.65;
  color: var(--fg);
  margin: 0 0 1.25rem;
}
.about__copy p strong { font-weight: 700; color: var(--ink); }
.about__copy a { border-bottom: 1px solid var(--accent); padding-bottom: 1px; }
.about__copy a:hover { color: var(--accent); }
/* Section headings inside .about__copy — used by the Terms page to
   break the long body into legible sub-sections. Display-thin so the
   prose still reads as a flowing block, not a stack of slabs. */
.about__copy h2 {
  font-family: var(--font-display);
  font-weight: 600;
  font-size: 1.05rem;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--accent);
  margin: 2.5rem 0 .9rem;
}
.about__copy h2:first-child { margin-top: 0; }
.terms__copyright {
  margin-top: 3rem !important;
  font-size: .85rem !important;
  opacity: .55;
}

/* ============== FOOTER ============== */
/* Light editorial footer: cream surface, dark text, oversized wordmark
   pinned to the right edge of the top row, social icons + copyright
   stacked underneath a thin rule. Fixed colors (not theme variables) so
   the footer reads consistently in both light and dark themes — same
   visual identity regardless of the body's mode. */
.foot {
  padding: 5rem var(--pad-x) 2rem;
  background: #f3f3f4;
  color: #1a1d24;
}
.foot__top {
  display: grid;
  gap: 3rem;
  grid-template-columns: 1fr;
  align-items: center;
  padding-bottom: 3rem;
  border-bottom: 1px solid rgba(26,29,36,.10);
}
/* Two-column (nav | logo) only when there's actually room for both on
   one line. Below 1000px, fall back to the stacked single-column layout
   so neither side gets squeezed against the other. */
@media (min-width: 1000px) { .foot__top { grid-template-columns: 1fr 1fr; } }

.foot__brand {
  display: flex;
  justify-content: flex-end;
}
.foot__brand img {
  height: 56px; width: auto;
  display: block;
  filter: none; /* dark logo on light bg */
}

/* Subpage chrome — slide-down menu socials stay white. */
body.is-subpage .global-menu__socials img { filter: invert(1); }

/* Subpages run an inverted footer (dark cream-on-ink) so the homepage's
   light editorial footer reads as the canonical brand surface and the
   subpage footers feel like the "outer" chrome. Same layout, opposite
   palette — every color and filter flips. */
body.is-subpage .foot {
  background: #1a1d24;
  color: #f3f3f4;
}
body.is-subpage .foot__top { border-bottom-color: rgba(243,243,244,.10); }
body.is-subpage .foot__brand img { filter: invert(1); }
body.is-subpage .foot__nav a   { color: #f3f3f4; }
body.is-subpage .foot__bottom  { color: #f3f3f4; }
body.is-subpage .foot__socials img { filter: brightness(0) invert(1); }
body.is-subpage .foot__socials a:hover { background: rgba(243,243,244,.08); }
.foot__nav { display: grid; grid-template-columns: 1fr 1fr; gap: .75rem 2.5rem; max-width: 24rem; }
.foot__nav a {
  font-family: var(--font-display); font-weight: 600;
  text-transform: uppercase; letter-spacing: .15em; font-size: .8rem;
  padding: .35rem 0;
  color: #1a1d24;
  opacity: .85;
  transition: opacity .2s, color .2s;
}
.foot__nav a:hover { opacity: 1; color: var(--accent); }

.foot__bottom {
  display: flex; flex-wrap: wrap; gap: 1rem;
  justify-content: space-between; align-items: center;
  padding-top: 1.5rem;
  font-size: .7rem; letter-spacing: .15em; text-transform: uppercase;
  color: #1a1d24; opacity: .55;
}
.foot__legal-link {
  margin-left: .75rem;
  text-decoration: underline;
  text-underline-offset: .25em;
  text-decoration-thickness: 1px;
  transition: opacity .2s;
}
.foot__legal-link:hover { opacity: .65; }
.foot__socials { display: flex; gap: .75rem; }
.foot__socials a {
  width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  transition: background .2s;
}
.foot__socials a:hover { background: rgba(26,29,36,.08); }
.foot__socials img {
  width: 18px; height: 18px;
  /* Force every icon to a uniform dark silhouette regardless of which
     file (dark / -w / colored) is referenced. */
  filter: brightness(0);
  opacity: .55;
  transition: opacity .2s;
}
.foot__socials a:hover img { opacity: 1; }

/* Mobile: stack the bottom row so copyright/legal text and the social
   icons each center on their own line. Desktop keeps the
   space-between layout (legal on the left, socials on the right). */
@media (max-width: 700px) {
  .foot__bottom {
    flex-direction: column;
    align-items: center;
    text-align: center;
    gap: 1.25rem;
  }
  .foot__bottom > span { text-align: center; }
  .foot__legal-link:first-of-type { margin-left: 0; }
  .foot__socials { justify-content: center; }
}

/* Stacked (mobile / narrow desktop): logo on top, nav underneath, both
   span the full row. Placed after the base footer rules so its
   max-width override actually wins the cascade — the base 24rem cap
   on .foot__nav was beating an earlier-placed override. */
@media (max-width: 999px) {
  .foot__brand    { order: -1; justify-content: center; }
  .foot__brand a  { width: 100%; display: block; }
  .foot__brand img {
    width: 100%; height: auto;
    max-width: 100%;
  }
  .foot__nav      { max-width: none; width: 100%; }
  .foot__nav a    { text-align: center; }
}

/* global menu socials — use white icons */
.global-menu__socials { display: flex; gap: 1rem; align-items: center; }
.global-menu__socials a {
  width: 38px; height: 38px;
  display: flex; align-items: center; justify-content: center;
  border-radius: 50%;
  border: 1px solid rgba(243,243,244,.15);
  transition: background .2s, border-color .2s;
}
.global-menu__socials a:hover { background: var(--accent); border-color: var(--accent); }
.global-menu__socials img { width: 16px; height: 16px; opacity: .9; }
.global-menu__socials a:hover img { filter: brightness(0); opacity: 1; }

/* ============== TWEAKS ============== */
.tweaks-panel {
  position: fixed; bottom: 1rem; right: 1rem;
  background: var(--bg); color: var(--fg);
  border: 1px solid var(--rule);
  border-radius: 12px;
  width: 280px;
  padding: 1rem;
  z-index: 200;
  font-family: var(--font-body);
  font-size: .85rem;
  box-shadow: 0 20px 50px rgba(0,0,0,.18);
}
.tweaks-panel__head {
  display: flex; justify-content: space-between; align-items: center;
  font-family: var(--font-display); font-weight: 700;
  text-transform: uppercase; letter-spacing: .2em; font-size: .7rem;
  margin-bottom: .85rem;
}
.tweaks-panel__close { font-size: 1.2rem; line-height: 1; padding: 0 .25rem; opacity: .6; }
.tweaks-panel__close:hover { opacity: 1; }
.tweak-row { display: flex; flex-direction: column; gap: .35rem; margin-bottom: .85rem; }
.tweak-row__label {
  font-family: var(--font-display); font-weight: 600;
  text-transform: uppercase; letter-spacing: .2em; font-size: .65rem;
  opacity: .55;
}
.tweak-seg { display: flex; gap: 2px; background: var(--cream-2); border-radius: 8px; padding: 2px; }
html.dark .tweak-seg { background: #1a1d24; }
.tweak-seg__btn {
  flex: 1; padding: .4rem .5rem;
  font-size: .7rem; font-weight: 600;
  border-radius: 6px; text-transform: capitalize;
  transition: background .15s, color .15s;
  white-space: nowrap;
}
.tweak-seg__btn.is-active { background: var(--ink); color: var(--cream); }
.tweak-swatches { display: flex; gap: .35rem; }
.tweak-swatch {
  width: 28px; height: 28px; border-radius: 50%;
  border: 2px solid transparent;
  transition: border-color .15s;
}
.tweak-swatch.is-active { border-color: var(--ink); }

/* Stagger entrance — toggled by IntersectionObserver in app.jsx (and
   a sibling observer in _footer.php for subpages). The observer
   assigns `animation-delay` inline based on visibility-batch order so
   a tile entering alone fires immediately while a row of siblings
   cascades sequentially.

   Implemented as a one-shot animation rather than a transition: a
   transition on `transform` would keep applying long after the reveal,
   slowing down JS-driven transforms (VanillaTilt mouse tracking) by
   the .7s easing. An animation runs once and then stops touching the
   element's computed style. */
.fade-up      { opacity: 0; }
.fade-up.in   {
  opacity: 1;
  /* `backwards` fill (not `both`) applies the from-state only during
     the delay; once the animation completes the element falls back to
     its underlying CSS — no transform locked in. That lets JS-driven
     transforms (VanillaTilt) take over freely after the reveal. */
  animation: hw-fade-up .7s cubic-bezier(.2,.8,.2,1) backwards;
}
@keyframes hw-fade-up {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

/* === Build animations === */

/* hw-rise stays defined so .case__meta on the project page can still
   reuse it (case-hero zoom + meta rise on /project/...). */
@keyframes hw-rise {
  from { opacity: 0; transform: translateY(24px); }
  to   { opacity: 1; transform: none; }
}
/* Hero entrance animations removed — compositing keyframes over the
   continuously-decoding background video was causing repaint pressure
   and stutter on lower-spec devices. The hero now paints instantly. */

/* Project case study hero — gentle zoom-out + meta rise. */
@keyframes hw-case-hero {
  from { transform: scale(1.06); opacity: 0; }
  to   { transform: scale(1);    opacity: 1; }
}
.case__hero { animation: hw-case-hero 1.2s cubic-bezier(.2,.8,.2,1) backwards; transform-origin: center; }
.case__meta { animation: hw-rise .9s cubic-bezier(.2,.8,.2,1) backwards; animation-delay: .4s; }

/* Gradient shimmer for the brand-color <em> accents. The gradient is
   stretched to 200% width so the animated background-position has
   somewhere to travel; the text-clip mask reveals the moving gradient
   through the letterforms. */
@keyframes hw-grad-shimmer {
  0%, 100% { background-position:   0% 50%; }
  50%      { background-position: 100% 50%; }
}
.about__sticker em,
.hero__sticker em,
.callout__sticker em,
.subpage__title em,
.case__title em,
.contact-success__title em {
  background-size: 200% 200%;
  animation: hw-grad-shimmer 9s ease-in-out infinite;
}

/* Awards icon reveal — start desaturated and faded, cascade to full
   tier color when the section enters viewport. The --i index drives a
   per-icon delay so the row "lights up" left-to-right. Implemented as
   a one-shot animation (not a transition) so it doesn't leave a
   lingering filter/opacity transition rule that would slow down the
   .award-hw hover state afterward. */
.awards .award-hw {
  filter: grayscale(1);
  opacity: 0;
}
.awards.in .award-hw {
  filter: none;
  opacity: .9;
  /* `backwards` fill instead of `both` — applies the grayed-out
     from-state during the per-icon delay, then releases the element
     to its static cascade after the animation. Hover transitions on
     opacity/filter then work without competing with a locked fill. */
  animation: hw-award-reveal .7s ease backwards;
  animation-delay: calc(var(--i, 0) * 25ms);
}
@keyframes hw-award-reveal {
  from { filter: grayscale(1); opacity: 0; }
  to   { filter: none;         opacity: .9; }
}

/* Cursor dot — small accent-colored circle that follows the mouse with
   ease, scaling up and dimming on hoverable elements. Pointer-events
   off so it never blocks the real cursor. Hidden on touch devices. */
.hw-cursor {
  position: fixed; left: 0; top: 0;
  /* Hexagonal cursor in the same 110:120 honeycomb proportion as the
     news thumbnails / service hex. clip-path replaces the circular
     border-radius. */
  width: 22px; height: 24px;
  background: var(--accent);
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  pointer-events: none;
  z-index: 9999;
  transform: translate3d(-50%, -50%, 0);
  transition: width .25s ease, height .25s ease, opacity .25s ease, background .25s ease;
  will-change: transform;
  mix-blend-mode: difference;
  opacity: .8;
}
.hw-cursor.is-hover {
  /* Magnify on hot targets but keep the 110:120 ratio. */
  width: 44px; height: 48px;
  opacity: .35;
}
@media (hover: none) { .hw-cursor { display: none; } }

/* prevent body scroll when menu is open */
body.menu-open { overflow: hidden; }

/* ============== REDUCED MOTION ==============
   Users who set OS-level "reduce motion" get the static version of
   everything: no infinite shimmer, no cursor follower, no hero ticker
   marquee, no cascade fades. Transitions are reduced to near-instant
   so hover state still feels alive but doesn't sweep the eye. */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
  /* Kill the infinite cosmetics outright (the wildcard above turns
     them into 0.01ms-duration loops which would still log GPU work). */
  .about__sticker em,
  .hero__sticker em,
  .callout__sticker em,
  .subpage__title em,
  .case__title em,
  .contact-success__title em,
  .hero__ticker-track,
  .tile:hover .tile__meta .cat::before {
    animation: none !important;
  }
  /* Cursor follower is purely decorative — hide it. JS also bails on
     prefers-reduced-motion so the dot is never created in the first
     place; this is belt-and-suspenders for already-rendered pages. */
  .hw-cursor { display: none !important; }
}

/* ============== SUBPAGES (news / project / about) ==============
   Shared scaffolding for the standalone pages rendered by news.php,
   project.php, news/index.php and about/index.php. Anchored on the
   same tokens as the SPA so theme switches stay coherent. */
.subpage {
  min-height: 60vh;
  padding: 8rem var(--pad-x) 5rem;
  background: var(--bg);
}
.subpage__back {
  display: inline-flex; align-items: center; gap: .4rem;
  font-family: var(--font-display); font-weight: 600;
  font-size: .7rem; letter-spacing: .2em; text-transform: uppercase;
  color: var(--fg); opacity: .7;
  margin-bottom: 2rem;
  transition: color .2s, opacity .2s;
}
.subpage__back:hover { color: var(--accent); opacity: 1; }
.subpage__back .arrow { transition: transform .2s ease; display: inline-block; }
.subpage__back:hover .arrow { transform: translateX(-3px); }
.subpage__head {
  border-bottom: 1px dotted var(--rule);
  padding-bottom: 2.5rem; margin-bottom: 3rem;
}
.subpage__eyebrow {
  display: inline-flex; align-items: center; gap: .65rem;
  font-family: var(--font-display); font-weight: 600;
  font-size: .7rem; letter-spacing: .25em; text-transform: uppercase;
  color: var(--accent);
  margin-bottom: 1rem;
}
/* Hex-framed eyebrow index — 110:120 honeycomb in currentColor, sized
   in em so it scales with the eyebrow's own font-size. The label sits
   inline next to it on the same line. */
.subpage__eyebrow-num {
  display: inline-flex;
  align-items: center; justify-content: center;
  position: relative;
  width: 2.2em; height: 2.4em; /* 110:120 ratio */
  font-weight: 200;
  letter-spacing: 0;
  font-size: 1em;
  flex: 0 0 auto;
}
.subpage__eyebrow-num::before {
  content: '';
  position: absolute; inset: 0;
  background: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 120'%3E%3Cpolygon points='55,2 108,30 108,90 55,118 2,90 2,30' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 120'%3E%3Cpolygon points='55,2 108,30 108,90 55,118 2,90 2,30' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain;     mask-size: contain;
  -webkit-mask-position: center;  mask-position: center;
  pointer-events: none;
}
.subpage__title {
  font-family: var(--font-display); font-weight: 200;
  /* Capped just under the hero (5.35rem) so the homepage primary
     remains the largest typography on the site. Slightly slower vw
     scaling too so subpages don't out-scale the hero on ultrawide. */
  font-size: clamp(2.25rem, 5vw, 4.75rem);
  line-height: .95; letter-spacing: -.02em;
  color: var(--ink);
  margin: 0 0 1rem;
}
.subpage__title em {
  font-style: normal; font-weight: 200;
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.subpage__sub {
  font-family: var(--font-body); font-weight: 300;
  font-size: 1.15rem; line-height: 1.5;
  color: var(--fg); opacity: .85;
  max-width: 60ch; margin: 0;
}

/* ----- Services list -----
   Each service is a wide row: oversized index number on the left, title
   + description + included-deliverables chip list on the right. Brand
   colors rotate through the rows so the page carries the full palette. */
.services-list { display: grid; gap: 4rem; margin-top: 1rem; }
.service-row {
  /* Two-column: hexagon-framed index number on the left, service body
     on the right. The hex inherits the row's accent color so the
     palette rotation flows from number → outline together. */
  display: grid;
  grid-template-columns: 110px 1fr;
  column-gap: 2.5rem;
  row-gap: 1rem;
  padding-bottom: 4rem;
  border-bottom: 1px dotted var(--rule);
  align-items: start;
}
.service-row:last-child { border-bottom: 0; padding-bottom: 0; }
.service-row__num {
  position: relative;
  width: 110px; height: 120px;
  display: flex; align-items: center; justify-content: center;
  font-family: var(--font-display); font-weight: 300;
  font-size: clamp(1.75rem, 3.5vw, 2.4rem);
  line-height: 1; letter-spacing: -.02em;
  color: var(--accent);
  flex-shrink: 0;
}
/* Outlined honeycomb cell drawn via a mask-image so the stroke color
   follows currentColor — that means the per-row color rotation below
   tints the hex automatically, no per-row override needed. */
.service-row__num::before {
  content: '';
  position: absolute; inset: 0;
  background: currentColor;
  -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 120'%3E%3Cpolygon points='55,2 108,30 108,90 55,118 2,90 2,30' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E");
          mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 110 120'%3E%3Cpolygon points='55,2 108,30 108,90 55,118 2,90 2,30' fill='none' stroke='black' stroke-width='2'/%3E%3C/svg%3E");
  -webkit-mask-repeat: no-repeat; mask-repeat: no-repeat;
  -webkit-mask-size: contain;     mask-size: contain;
  -webkit-mask-position: center;  mask-position: center;
  pointer-events: none;
}
.service-row:nth-child(2) .service-row__num { color: var(--accent-2); }
.service-row:nth-child(3) .service-row__num { color: var(--accent-3); }
.service-row:nth-child(4) .service-row__num {
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
/* The 4th row paints the digit with a gradient via background-clip: text,
   which forces `color: transparent` on the parent. That kills the hex
   outline because the ::before pulls its fill from currentColor. Paint
   the outline ::before directly with the gradient instead. */
.service-row:nth-child(4) .service-row__num::before {
  background: var(--gradient);
}
.service-row:nth-child(5) .service-row__num { color: var(--accent-2); }
.service-row:nth-child(6) .service-row__num { color: var(--accent-3); }
.service-row__body { min-width: 0; }
.service-row__title {
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(1.75rem, 3.5vw, 2.75rem);
  line-height: 1.05; letter-spacing: -.02em;
  color: var(--ink);
  margin: 0 0 1rem;
}
.service-row__desc {
  font-family: var(--font-body); font-weight: 300;
  font-size: 1.05rem; line-height: 1.6;
  color: var(--fg); opacity: .9;
  max-width: 60ch;
  margin: 0 0 1.5rem;
}
.service-row__includes {
  display: flex; flex-wrap: wrap;
  gap: .5rem;
  list-style: none; padding: 0; margin: 0;
}
.service-row__includes li {
  font-family: var(--font-body); font-weight: 400;
  font-size: .8rem; line-height: 1;
  padding: .55rem .9rem;
  border: 1px solid var(--rule);
  border-radius: 999px;
  color: var(--ink); background: transparent;
  transition: border-color .2s, color .2s, background .2s;
}
.service-row__includes li:hover { border-color: var(--accent); color: var(--accent); }
.service-row:nth-child(2) .service-row__includes li:hover { border-color: var(--accent-2); color: var(--accent-2); }
.service-row:nth-child(3) .service-row__includes li:hover { border-color: var(--accent-3); color: var(--accent-3); }
.service-row:nth-child(5) .service-row__includes li:hover { border-color: var(--accent-2); color: var(--accent-2); }
.service-row:nth-child(6) .service-row__includes li:hover { border-color: var(--accent-3); color: var(--accent-3); }
@media (max-width: 700px) {
  .service-row {
    grid-template-columns: 76px 1fr;
    column-gap: 1.25rem;
  }
  .service-row__num {
    /* Keep the 110:120 desktop ratio at the smaller size. */
    width: 76px; height: 83px;
    font-size: 1.6rem;
  }
}

/* ----- News listing -----
   Single-column flow inside the .case container so the listing reads
   with the same editorial scale as the article pages — date eyebrow
   stacked above a display-weight headline, optional teaser underneath.
   Hover shifts the row 4px right and pulls the headline into accent. */
.news-list { list-style: none; padding: 0; margin: 0; }
.news-list__item {
  border-top: 1px dotted var(--rule);
}
/* The first item sits right under .subpage__head, which already has its
   own dotted bottom border. Drop the top border + the top padding so
   we don't double-rule and the list starts tight to the header. */
.news-list__item:first-child { border-top: 0; }
.news-list__item:first-child a { padding-top: .25rem; }
.news-list__item:last-child { border-bottom: 1px dotted var(--rule); }
.news-list__item a {
  display: grid;
  grid-template-columns: 110px 1fr;
  gap: 1.5rem;
  padding: 2rem 0;
  align-items: center;
  /* transform instead of padding-left for the hover shift — transforms
     don't reflow layout, so the text column width stays constant and
     the headline/teaser don't snap to a new wrap mid-animation. */
  transition: transform .25s ease, color .2s ease;
  will-change: transform;
}
.news-list__item:hover a { transform: translateX(.5rem); }
.news-list__text {
  display: grid;
  gap: .4rem;
  min-width: 0;
}

/* Hexagonal article thumb. clip-path renders a flat-top hexagon (the
   classic honeycomb-cell shape, on-brand for the name). The image
   fills the cell via object-fit: cover so any aspect ratio works. If
   the article has no social image, the cell falls back to the brand
   gradient so every row keeps the same visual rhythm. */
.news-list__thumb {
  display: block;
  width: 110px; height: 120px;
  background: var(--cream-2);
  clip-path: polygon(50% 0%, 100% 25%, 100% 75%, 50% 100%, 0% 75%, 0% 25%);
  overflow: hidden;
  transition: transform .25s ease;
}
.news-list__thumb img {
  width: 100%; height: 100%;
  object-fit: cover;
  display: block;
}
.news-list__thumb.is-placeholder { background: var(--gradient); opacity: .55; }
.news-list__item:hover .news-list__thumb { transform: scale(1.04); }
html.dark .news-list__thumb { background: #1a1d24; }

@media (max-width: 700px) {
  .news-list__item a { grid-template-columns: 76px 1fr; gap: 1rem; }
  .news-list__thumb { width: 76px; height: 84px; }
}
.news-list__date {
  font-family: var(--font-display); font-weight: 600;
  font-size: .7rem; letter-spacing: .25em; text-transform: uppercase;
  color: var(--accent); opacity: .9;
}
.news-list__head {
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(1.5rem, 3vw, 2.2rem); line-height: 1.1;
  letter-spacing: -.015em; color: var(--ink);
  display: block;
  transition: color .2s ease;
}
.news-list__item:hover .news-list__head { color: var(--accent); }
.news-list__sub {
  font-family: var(--font-body); font-weight: 300;
  font-size: 1rem; line-height: 1.5; color: var(--fg);
  opacity: .85;
  margin-top: .25rem;
  display: block;
  width: 100%;
}

/* ----- News article -----
   News uses the project case-study layout: full-width hero + 2-column
   meta where the left column carries the title + body and the right
   column shows a recent-news rail. The .article wrapper is gone; only
   the .article__body class survives so the type-reset rules below can
   wrangle whatever HTML the CMS hands us. */
.article__body {
  font-family: var(--font-body); font-weight: 300;
  font-size: 1.125rem; line-height: 1.7;
  color: var(--fg);
}
/* Aggressive reset of CMS inline cruft. The legacy honeywired_news.description
   column ships with <font>, inline style="font-size:..." and nested wrappers
   from the old WYSIWYG. Force the editorial scale by overriding common
   offenders. !important is the right call here because the inline styles
   are baked into the stored HTML — we can't selectively edit each row. */
.article__body * {
  font-family: inherit !important;
  font-size: inherit !important;
  line-height: inherit !important;
  color: inherit !important;
  background: transparent !important;
}
.article__body p { margin: 0 0 1.4rem; }
.article__body p:first-child { margin-top: 0; }
.article__body p:last-child  { margin-bottom: 0; }
.article__body strong, .article__body b {
  color: var(--ink) !important;
  font-weight: 600 !important;
}
.article__body em, .article__body i { font-style: italic; }
.article__body a {
  color: var(--accent) !important;
  border-bottom: 1px solid var(--accent);
  padding-bottom: 1px;
}
.article__body a:hover { color: var(--accent-deep) !important; }

/* Editorial heading scale — overrides the old site's chunky h2/h3 sizing. */
.article__body h2 {
  font-family: var(--font-display) !important;
  font-weight: 200 !important;
  font-size: clamp(1.5rem, 2.5vw, 2rem) !important;
  line-height: 1.15 !important;
  letter-spacing: -.01em;
  color: var(--ink) !important;
  margin: 2.5rem 0 1rem;
}
.article__body h3 {
  font-family: var(--font-display) !important;
  font-weight: 600 !important;
  font-size: 1rem !important;
  letter-spacing: .15em;
  text-transform: uppercase;
  color: var(--accent) !important;
  margin: 2rem 0 .75rem;
}
.article__body h4 {
  font-family: var(--font-display) !important;
  font-weight: 600 !important;
  font-size: .85rem !important;
  letter-spacing: .2em;
  text-transform: uppercase;
  color: var(--fg) !important;
  margin: 1.5rem 0 .5rem;
}
.article__body ul, .article__body ol {
  margin: 0 0 1.4rem 1.5rem;
  padding: 0;
}
.article__body li { margin-bottom: .35rem; }

/* Inline media reset — strip width/height attributes that trigger
   distortion, force responsive scaling. */
.article__body img,
.article__body video,
.article__body iframe {
  max-width: 100% !important;
  width: 100% !important;
  height: auto !important;
  display: block;
  margin: 2rem 0;
  border-radius: 4px;
}

/* Pull-quote — reuse the .case__desc treatment so news + project
   articles share one quote object. Supports both the legacy
   <div class="quote">…<div class="quoteCredit"></div></div> markup
   and semantic <blockquote><cite></cite></blockquote>. */
.article__body .quote,
.article__body blockquote {
  position: relative;
  margin: 2.5rem 0;
  padding: .5rem 0 .5rem 2.5rem;
  font-family: var(--font-display) !important;
  font-weight: 200 !important;
  font-size: clamp(1.25rem, 2vw, 1.65rem) !important;
  line-height: 1.3 !important;
  letter-spacing: -.005em;
  color: var(--ink) !important;
  background: var(--gradient) left / 2px 100% no-repeat;
  font-style: italic;
}
.article__body .quote::before,
.article__body blockquote::before {
  content: '\201C';
  position: absolute; left: .35rem; top: -.4rem;
  font-family: var(--font-display); font-style: normal;
  font-size: 3.5rem; line-height: 1;
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent !important;
}
.article__body .quote i,
.article__body .quote em,
.article__body blockquote p {
  font-style: italic;
  display: block;
  margin: 0 0 .75rem;
}
.article__body .quote .quoteCredit,
.article__body blockquote cite,
.article__body blockquote footer {
  display: block;
  margin-top: .5rem;
  font-family: var(--font-display) !important;
  font-weight: 600 !important;
  font-size: .7rem !important;
  letter-spacing: .2em;
  text-transform: uppercase;
  font-style: normal;
  color: var(--fg) !important;
  opacity: .75;
}

/* ----- Project case study ----- */
.case { max-width: 1100px; margin: 0 auto; }
.case__hero {
  width: 100%; aspect-ratio: 16/9;
  background: var(--ink-2); background-size: cover; background-position: center;
  border-radius: 4px; margin-bottom: 2.5rem;
  position: relative; overflow: hidden;
}
.case__hero video {
  position: absolute; inset: 0; width: 100%; height: 100%;
  object-fit: cover;
}
.case__meta {
  display: grid; grid-template-columns: 1fr; gap: 2rem;
  margin-bottom: 3rem;
}
@media (min-width: 800px) { .case__meta { grid-template-columns: 1.5fr 1fr; gap: 4rem; } }
.case__client {
  font-family: var(--font-display); font-weight: 600;
  font-size: .75rem; letter-spacing: .3em; text-transform: uppercase;
  margin-bottom: .75rem;
  /* Gradient text — echoes the hero's <em> treatment so the brand
     palette shows up on every project page, not just the homepage. */
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
/* Inline <em> in the title gets the same gradient treatment, in case
   editors want to highlight a word the way the homepage hero does. */
.case__title em {
  font-style: normal;
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.case__title {
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(2rem, 4.5vw, 3.5rem); line-height: 1.05;
  letter-spacing: -.02em; color: var(--ink); margin: 0 0 1.5rem;
}
/* Subtitle under .case__title — used by news articles for the short
   teaser line. Sits between the title and body in the same column. */
.case__subtitle {
  font-family: var(--font-body); font-weight: 300;
  font-size: 1.15rem; line-height: 1.5;
  color: var(--fg); opacity: .85;
  max-width: 60ch;
  margin: 0 0 1.5rem;
}
.case__desc {
  font-family: var(--font-body); font-weight: 300;
  font-size: 1.05rem; line-height: 1.65; color: var(--fg);
}
.case__desc p { margin: 0 0 1rem; }
.case__desc a { color: var(--accent); border-bottom: 1px solid var(--accent); padding-bottom: 1px; }
.case__desc a:hover { color: var(--accent-deep); }

/* Pull-quote treatment — works for both the legacy
   <div class="quote">…<div class="quoteCredit">…</div></div> markup
   coming out of the CMS and the semantic <blockquote><cite>…</cite></blockquote>
   form. The oversized opening glyph reads as a visual cue without
   needing CMS authors to add any wrapper class. */
.case__desc .quote,
.case__desc blockquote {
  position: relative;
  margin: 2.5rem 0;
  padding: .5rem 0 .5rem 2.5rem;
  font-family: var(--font-display); font-weight: 200;
  font-size: clamp(1.25rem, 2vw, 1.75rem);
  line-height: 1.3; letter-spacing: -.005em;
  color: var(--ink);
  border-left: 2px solid var(--accent);
  font-style: italic;
}
.case__desc .quote::before,
.case__desc blockquote::before {
  content: '\201C'; /* left double quotation mark */
  position: absolute; left: .35rem; top: -.4rem;
  font-family: var(--font-display); font-weight: 200; font-style: normal;
  font-size: 3.5rem; line-height: 1;
  /* Gradient-clipped opening glyph — small touch but it pulls the
     brand palette into the editorial body, not just the chrome. */
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent; color: transparent;
}
.case__desc .quote,
.case__desc blockquote {
  /* Replace the flat accent left rule with a vertical gradient bar so
     the quote callout reads as the same brand object as the eyebrows
     and gradient text. --gradient is already a linear-gradient() image,
     so it goes straight into the background shorthand. */
  border-left: 0;
  background: var(--gradient) left / 2px 100% no-repeat;
}
.case__desc .quote i,
.case__desc .quote em,
.case__desc blockquote p {
  font-style: italic; display: block;
  margin: 0 0 .75rem;
}
.case__desc .quote p:last-child,
.case__desc blockquote p:last-child { margin-bottom: 0; }

/* Attribution: legacy `.quoteCredit` div, and semantic <cite>. */
.case__desc .quote .quoteCredit,
.case__desc blockquote cite,
.case__desc blockquote footer {
  display: block;
  margin-top: .5rem;
  font-family: var(--font-display); font-weight: 600;
  font-size: .7rem; letter-spacing: .2em; text-transform: uppercase;
  font-style: normal;
  color: var(--fg); opacity: .7;
}
.case__details { display: grid; gap: 1.75rem; align-content: start; }
.case__details-block { position: relative; }
/* Each block's heading sits on the same line as a colored rule that
   extends to the right edge. The rule uses the brand palette by
   block order — Roles → pink, Credits → blue, Press → mint, Awards
   → gradient — so the sidebar carries all three colors. */
.case__details-eyebrow {
  display: flex;
  align-items: center;
  gap: .75rem;
  margin-bottom: .75rem;
  font-family: var(--font-display); font-weight: 600;
  font-size: .65rem; letter-spacing: .3em; text-transform: uppercase;
  color: var(--accent);
}
.case__details-eyebrow::after {
  content: '';
  flex: 1;
  height: 2px;
  background: var(--accent);
}
.case__details-block:nth-of-type(2) .case__details-eyebrow { color: var(--accent-2); }
.case__details-block:nth-of-type(2) .case__details-eyebrow::after { background: var(--accent-2); }
.case__details-block:nth-of-type(3) .case__details-eyebrow { color: var(--accent-3); }
.case__details-block:nth-of-type(3) .case__details-eyebrow::after { background: var(--accent-3); }
.case__details-block:nth-of-type(4) .case__details-eyebrow {
  background: var(--gradient);
  -webkit-background-clip: text; background-clip: text;
  -webkit-text-fill-color: transparent;
}
.case__details-block:nth-of-type(4) .case__details-eyebrow::after {
  background: var(--gradient);
  -webkit-text-fill-color: initial;
}
.case__details-body {
  color: var(--ink); line-height: 1.5;
  font-family: var(--font-body); font-size: .9rem;
}
.case__details-body img { max-width: 80px; height: auto; margin: .25rem .5rem .25rem 0; vertical-align: middle; }

/* Recent-news rail for the article subpage. Sits inside .case__details
   under the "Recent News" eyebrow. Each item is a stacked date + title
   that hover-pulls into the accent. */
.news-recent { list-style: none; padding: 0; margin: 0; display: grid; gap: 0; }
.news-recent__item {
  border-top: 1px dotted var(--rule);
  padding: .85rem 0;
}
.news-recent__item:first-child { border-top: 0; padding-top: 0; }
.news-recent__item:last-child  { padding-bottom: 0; }
.news-recent__item a {
  display: grid;
  gap: .25rem;
  transition: transform .2s ease;
}
.news-recent__item:hover a { transform: translateX(3px); }
.news-recent__date {
  font-family: var(--font-display); font-weight: 600;
  font-size: .65rem; letter-spacing: .25em; text-transform: uppercase;
  color: var(--fg); opacity: .6;
}
.news-recent__title {
  font-family: var(--font-display); font-weight: 300;
  font-size: 1rem; line-height: 1.25;
  letter-spacing: -.005em;
  color: var(--ink);
}
.news-recent__item:hover .news-recent__title { color: var(--accent); }
/* ----- Case study: structured blocks (press / awards / media) -----
   These render data from the normalized tables (honeywired_press,
   honeywired_award_wins, honeywired_media). The legacy fallbacks above
   keep using `.case__content`. */
.case__block { margin: 4rem 0 0; }
.case__block-head { margin-bottom: 1.5rem; }
.case__block-eyebrow {
  font-family: var(--font-display); font-weight: 600;
  font-size: .7rem; letter-spacing: .3em; text-transform: uppercase;
  color: var(--accent);
}

/* Awards row — lives in the right-hand .case__details column. Uses the
   same .award-hw styling as the homepage so tier colors stay in sync.
   Compact size since the sidebar is narrow. */
.case__awards-row {
  display: flex; flex-wrap: wrap; gap: .5rem;
  align-items: center;
}
.case__awards-row .award-hw {
  height: 18px; width: 46px;
}

/* Press wall — monochrome logo grid. Logos tint to --fg by default and
   wash full-color (no filter) on hover so the wall reads as a uniform
   typographic block until you interact with it. The .is-compact variant
   targets the right-hand sidebar where space is tight. */
.case__press-grid {
  display: grid; gap: 1.25rem 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  align-items: center;
}
.case__press-grid.is-compact {
  gap: .9rem 1rem;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
}
.case__press-cell {
  display: flex; align-items: center; justify-content: center;
  height: 44px;
  opacity: .55; transition: opacity .2s ease, filter .2s ease, transform .2s ease;
  filter: grayscale(1);
}
.case__press-grid.is-compact .case__press-cell { height: 28px; }
.case__press-cell img {
  max-width: 100%; max-height: 100%; height: auto; width: auto;
  object-fit: contain;
}
.case__press-cell:hover {
  opacity: 1; filter: none;
  transform: translateY(-2px);
}
html.dark .case__press-cell { filter: grayscale(1) invert(.85); }
html.dark .case__press-cell:hover { filter: none; }

/* Legacy press fallback — keep the old <div style="background-image…">
   blocks usable while migration is in progress. Approximates the
   normalized look so projects render consistently regardless of source. */
.case__press-legacy {
  display: grid; gap: 1.25rem 1.5rem;
  grid-template-columns: repeat(auto-fill, minmax(110px, 1fr));
  align-items: center;
}
.case__press-legacy.is-compact {
  gap: .9rem 1rem;
  grid-template-columns: repeat(auto-fill, minmax(64px, 1fr));
}
.case__press-legacy > div {
  height: 44px;
  background-repeat: no-repeat; background-position: center;
  cursor: pointer; opacity: .55; filter: grayscale(1);
  transition: opacity .2s ease, filter .2s ease;
}
.case__press-legacy.is-compact > div { height: 28px; }
.case__press-legacy > div:hover { opacity: 1; filter: none; }
html.dark .case__press-legacy > div { filter: grayscale(1) invert(.85); }

/* Media gallery — 12-column CMS-driven masonry. The `layout` field on
   honeywired_media drives grid-column spans so authors can compose
   rhythm: full hero, then a 50/50 pair, then a quarter row of details,
   etc. Defaults to `full` when a row's layout is empty so existing
   rows render unchanged. Items align to the top of their row, so a
   shorter half won't stretch to match a taller neighbor. */
.case__media {
  margin-top: 4rem;
  display: grid;
  grid-template-columns: repeat(12, 1fr);
  gap: 1.5rem;
  align-items: start;
  /* `dense` packs smaller items into earlier gaps when an item later
     in the source order would fit there. Lets the auto-mosaic look
     clean even when row sums don't divide evenly into 12. */
  grid-auto-flow: dense;
}
.case__media-item {
  grid-column: span 12;
  position: relative; overflow: hidden;
  border-radius: 4px;
  background: var(--cream-2);
  min-width: 0;
}
html.dark .case__media-item { background: #14161b; }
.case__media-item img,
.case__media-item video {
  width: 100%; height: auto; display: block;
}
/* Affordance for lightbox-eligible items (images + videos). Desktop
   only — under 700px we render a single-column scroll, no lightbox,
   so the zoom-in cursor and hover-scale would lie about behavior. */
@media (min-width: 700px) {
  .case__media-item.is-image:not(.is-no-lb),
  .case__media-item.is-video:not(.is-no-lb) {
    cursor: zoom-in;
  }
  .case__media-item.is-image:not(.is-no-lb) img,
  .case__media-item.is-video:not(.is-no-lb) video {
    transition: transform .35s cubic-bezier(.2,.8,.2,1);
  }
  .case__media-item.is-image:not(.is-no-lb):hover img { transform: scale(1.02); }
}

/* ---- Lightbox ---- */
.lightbox {
  position: fixed; inset: 0;
  background: rgba(14, 16, 20, .96);
  z-index: 9990;
  display: none;
  align-items: center; justify-content: center;
  padding: 4rem var(--pad-x);
  opacity: 0;
  transition: opacity .25s ease;
}
.lightbox.is-open { display: flex; opacity: 1; }
.lightbox__stage {
  max-width: 100%; max-height: 100%;
  display: flex; align-items: center; justify-content: center;
  pointer-events: none; /* let clicks pass through to backdrop */
}
.lightbox__stage > * { pointer-events: auto; }
.lightbox__stage img,
.lightbox__stage video {
  max-width: min(1400px, 100%); max-height: 80vh;
  display: block;
  border-radius: 4px;
  box-shadow: 0 20px 60px rgba(0,0,0,.5);
}
.lightbox__close,
.lightbox__prev,
.lightbox__next {
  position: absolute;
  background: rgba(243,243,244,.06);
  border: 1px solid rgba(243,243,244,.12);
  color: #f3f3f4; cursor: pointer;
  width: 44px; height: 44px;
  border-radius: 50%;
  display: flex; align-items: center; justify-content: center;
  font-size: 1.1rem; line-height: 1;
  transition: background .2s, border-color .2s, transform .2s;
  z-index: 2;
}
.lightbox__close:hover,
.lightbox__prev:hover,
.lightbox__next:hover {
  background: rgba(243,243,244,.16);
  border-color: rgba(243,243,244,.3);
}
.lightbox__close { top: 1.25rem; right: 1.25rem; }
.lightbox__prev,
.lightbox__next { top: 50%; transform: translateY(-50%); }
.lightbox__prev { left: 1.25rem; }
.lightbox__next { right: 1.25rem; }
.lightbox__prev:hover { transform: translateY(-50%) translateX(-2px); }
.lightbox__next:hover { transform: translateY(-50%) translateX(2px); }
.lightbox__counter {
  position: absolute; top: 1.25rem; left: 1.25rem;
  color: rgba(243,243,244,.55);
  font-family: var(--font-display); font-weight: 600;
  font-size: .7rem; letter-spacing: .25em; text-transform: uppercase;
  font-variant-numeric: tabular-nums;
}
.lightbox__caption {
  position: absolute; bottom: 1.5rem; left: 50%; transform: translateX(-50%);
  max-width: 70ch;
  color: rgba(243,243,244,.7);
  font-family: var(--font-body); font-weight: 300;
  font-size: .85rem; line-height: 1.4;
  text-align: center;
  padding: 0 var(--pad-x);
}
body.lightbox-open { overflow: hidden; }
@media (max-width: 600px) {
  .lightbox { padding: 3rem .5rem 4rem; }
  .lightbox__close { top: .5rem; right: .5rem; }
  .lightbox__prev  { left: .25rem; }
  .lightbox__next  { right: .25rem; }
  .lightbox__counter { top: .75rem; left: .75rem; font-size: .6rem; }
}

/* Layout variants. `wide` is full-bleed-but-padded (centered at ~83%);
   `pair-l`/`pair-r` are conventionally placed side-by-side and read
   as a single composition. */
.case__media-item.is-full    { grid-column: span 12; }
.case__media-item.is-wide    { grid-column: 2 / span 10; }
.case__media-item.is-half,
.case__media-item.is-pair-l,
.case__media-item.is-pair-r  { grid-column: span 6; }
.case__media-item.is-third   { grid-column: span 4; }
.case__media-item.is-quarter { grid-column: span 3; }

/* Paired image cells: lock to a common 3:2 aspect with object-fit:cover
   so a row of two images always reads as one band of equal height,
   even when the source images differ slightly in aspect. Tall portrait
   images get cropped here — for those, set the row's `layout` in DB to
   'full' so it spans 12 cols and keeps its natural aspect.
   Half-width *videos* (rare; only happens if explicit layout='half')
   keep their natural aspect since cover-cropping a clip is bad. */
.case__media-item.is-half.is-image,
.case__media-item.is-pair-l.is-image,
.case__media-item.is-pair-r.is-image {
  aspect-ratio: 3 / 2;
  overflow: hidden;
}
.case__media-item.is-half.is-image img,
.case__media-item.is-pair-l.is-image img,
.case__media-item.is-pair-r.is-image img {
  width: 100%; height: 100%;
  object-fit: cover;
}

@media (max-width: 900px) {
  .case__media-item.is-third   { grid-column: span 6; }
  .case__media-item.is-quarter { grid-column: span 6; }
  .case__media-item.is-wide    { grid-column: span 12; }
}
@media (max-width: 600px) {
  .case__media-item.is-half,
  .case__media-item.is-pair-l,
  .case__media-item.is-pair-r,
  .case__media-item.is-third,
  .case__media-item.is-quarter { grid-column: span 12; }

  /* Reset the desktop 3:2 cover-crop on paired image cells. With the
     row collapsed to single-column on mobile, images should render
     at their natural aspect — a 1×1 stays 1×1, a portrait stays
     portrait. The cover-crop existed only to keep two side-by-side
     halves the same height; with one-per-row that's irrelevant. */
  .case__media-item.is-half.is-image,
  .case__media-item.is-pair-l.is-image,
  .case__media-item.is-pair-r.is-image {
    aspect-ratio: auto;
    overflow: visible;
  }
  .case__media-item.is-half.is-image img,
  .case__media-item.is-pair-l.is-image img,
  .case__media-item.is-pair-r.is-image img {
    height: auto;
    object-fit: contain;
  }
}
/* 16:9 frame for video embeds (vimeo/youtube/generic iframe). */
.case__embed {
  position: relative; width: 100%;
  aspect-ratio: 16 / 9;
  background: #000;
}
.case__embed iframe {
  position: absolute; inset: 0;
  width: 100%; height: 100%; border: 0;
}
.case__media-caption {
  font-family: var(--font-body); font-weight: 300;
  font-size: .85rem; line-height: 1.5;
  color: var(--fg); opacity: .7;
  padding: .75rem 0 0;
}
