/* ==========================================================================
   Stickart Signwriting
   "Let colours flow into your life" — the site starts dark and colourless,
   and colour arrives through motion, scroll and the visitor's own hand.
   Hand-written. No framework, no build step for CSS.
   ========================================================================== */

/* ------------------------------------------------------------------ tokens */
:root {
  /* The seven vinyl colours, lifted straight off the fan of rolls in the logo. */
  --v1: #E8112D;  /* red     */
  --v2: #F58220;  /* orange  */
  --v3: #FFDD00;  /* yellow  */
  --v4: #00A651;  /* green   */
  --v5: #29ABE2;  /* cyan    */
  --v6: #0055B8;  /* blue    */
  --v7: #EC008C;  /* magenta */

  /* Logo word-art colours. */
  --brand-pink:   #EC008C;
  --brand-purple: #5B2D8E;
  --brand-orange: #F1571E;
  --brand-gold:   #FFC20E;

  --flow: linear-gradient(90deg,
    var(--v1) 0%, var(--v2) 16%, var(--v3) 32%, var(--v4) 50%,
    var(--v5) 66%, var(--v6) 83%, var(--v7) 100%);

  /* Ground. Deliberately near-black so saturated vinyl reads at full strength. */
  --bg:      #0B0B0F;
  --bg-2:    #101017;
  --bg-3:    #16161F;
  --line:    #26262F;
  --line-2:  #34343F;
  --line-3:  #6A6A7A;  /* resting border on form fields: 3.70:1 on --bg, 3.57:1 on --bg-2 */

  --tx:      #F4F4F7;
  --tx-2:    #B4B4C2;
  --tx-3:    #7C7C8C;

  --sp:  clamp(1.15rem, 4.2vw, 2rem);
  --gut: clamp(1.15rem, 4.2vw, 3.25rem);
  --max: 1280px;

  --r-s:  8px;
  --r-m:  16px;
  --r-l:  26px;
  --r-xl: 38px;

  --sh-1: 0 1px 2px rgb(0 0 0 / .5);
  --sh-2: 0 10px 30px -12px rgb(0 0 0 / .7);
  --sh-3: 0 34px 70px -28px rgb(0 0 0 / .85);

  --ease:     cubic-bezier(.22, .61, .36, 1);
  --ease-out: cubic-bezier(.16, 1, .3, 1);
  --ease-in:  cubic-bezier(.7, 0, .84, 0);

  --nav-h: 74px;
}

/* ------------------------------------------------------------------- reset */
*, *::before, *::after { box-sizing: border-box; }
* { margin: 0; }

html {
  -webkit-text-size-adjust: 100%;
  scroll-behavior: smooth;
  scroll-padding-top: calc(var(--nav-h) + 14px);
}

body {
  background: var(--bg);
  color: var(--tx);
  font-family: Archivo, ui-sans-serif, system-ui, -apple-system, "Segoe UI", Roboto, sans-serif;
  font-size: clamp(1rem, .96rem + .2vw, 1.075rem);
  line-height: 1.62;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  overflow-x: hidden;
}

img, svg, iframe { display: block; max-width: 100%; }
img { height: auto; }
button, input, select, textarea { font: inherit; color: inherit; }
button { background: none; border: 0; cursor: pointer; }
a { color: inherit; }
ul, ol { list-style: none; padding: 0; }

:focus-visible {
  outline: 3px solid var(--v3);
  outline-offset: 3px;
  border-radius: 4px;
}

::selection { background: var(--v7); color: #fff; }

.skip {
  position: absolute; left: -9999px; top: 0; z-index: 999;
  background: var(--v3); color: #000; padding: .8rem 1.2rem;
  font-weight: 700; border-radius: 0 0 var(--r-s) 0;
}
.skip:focus { left: 0; }

/* --------------------------------------------------------------- typography */
.h2 {
  font-family: Anton, Impact, "Archivo Black", sans-serif;
  font-weight: 400;
  font-size: clamp(2.35rem, 1.3rem + 5.1vw, 5.1rem);
  line-height: .95;
  letter-spacing: -.012em;
  text-transform: uppercase;
  text-wrap: balance;
}

.kicker {
  display: inline-flex; align-items: center; gap: .6rem;
  font-size: .78rem; font-weight: 700; letter-spacing: .19em;
  text-transform: uppercase; color: var(--tx-2);
  margin-bottom: 1.15rem;
}
.kicker i {
  width: 26px; height: 5px; border-radius: 3px;
  background: var(--c, var(--v7)); flex: none;
}

.lede {
  font-size: clamp(1.03rem, .98rem + .34vw, 1.24rem);
  color: var(--tx-2);
  max-width: 56ch;
  text-wrap: pretty;
}

.wrap {
  width: min(100% - var(--gut) * 2, var(--max));
  margin-inline: auto;
}

.sechead { margin-bottom: clamp(2.4rem, 5vw, 4rem); }
.sechead--row {
  display: grid; gap: 1.6rem;
  grid-template-columns: 1fr;
  align-items: end;
}
@media (min-width: 900px) {
  .sechead--row { grid-template-columns: 1.15fr .85fr; gap: 3rem; }
}

section { padding-block: clamp(4.2rem, 9vw, 8.5rem); }

/* ----------------------------------------------------------------- buttons */
.btn {
  --btn-bg: var(--tx);
  --btn-fg: #0B0B0F;
  display: inline-flex; align-items: center; justify-content: center; gap: .6rem;
  padding: .82rem 1.5rem;
  background: var(--btn-bg); color: var(--btn-fg);
  border-radius: 100px;
  font-weight: 700; font-size: .95rem; letter-spacing: .01em;
  text-decoration: none; white-space: nowrap;
  transition: transform .28s var(--ease-out), box-shadow .28s var(--ease-out), background-color .28s var(--ease);
  box-shadow: var(--sh-1);
}
.btn svg { width: 19px; height: 19px; flex: none; }
.btn:hover { transform: translateY(-2px); box-shadow: var(--sh-2); }
.btn:active { transform: translateY(0); }

.btn--call {
  --btn-bg: var(--v7);
  --btn-fg: #fff;
  position: relative; overflow: hidden;
}
/* A pass of the full colour flow sweeps across on hover — vinyl going down. */
.btn--call::after {
  content: ''; position: absolute; inset: 0;
  background: var(--flow);
  transform: translateX(-101%);
  transition: transform .55s var(--ease-out);
  z-index: -1;
}
.btn--call:hover::after { transform: translateX(0); }
.btn--call > * { position: relative; z-index: 1; }

.btn--ghost {
  --btn-bg: transparent;
  --btn-fg: var(--tx);
  border: 1.5px solid var(--line-2);
}
.btn--ghost:hover { --btn-bg: #ffffff10; border-color: var(--tx-3); }

.btn--lg { padding: 1.05rem 1.9rem; font-size: 1.02rem; }
.btn--block { width: 100%; }

.chip {
  padding: .55rem 1.15rem;
  border: 1.5px solid var(--line);
  border-radius: 100px;
  color: var(--tx-2);
  font-size: .9rem; font-weight: 600;
  transition: color .25s var(--ease), border-color .25s var(--ease), background-color .25s var(--ease);
}
.chip:hover { color: var(--tx); border-color: var(--line-2); }
.chip.is-on {
  background: var(--tx); color: #0B0B0F; border-color: var(--tx);
}

/* --------------------------------------------------------------------- nav */
.nav {
  position: fixed; inset: 0 0 auto; z-index: 100;
  height: var(--nav-h);
  background: color-mix(in srgb, var(--bg) 78%, transparent);
  backdrop-filter: blur(16px) saturate(150%);
  -webkit-backdrop-filter: blur(16px) saturate(150%);
  border-bottom: 1px solid transparent;
  transition: background-color .3s var(--ease), border-color .3s var(--ease);
}
.nav.is-stuck { background: color-mix(in srgb, var(--bg) 94%, transparent); border-bottom-color: var(--line); }

.nav__in {
  width: min(100% - var(--gut) * 2, var(--max));
  margin-inline: auto; height: calc(var(--nav-h) - 4px);
  display: flex; align-items: center; gap: clamp(.8rem, 2.4vw, 2.2rem);
}

.nav__brand { display: flex; align-items: center; gap: .6rem; text-decoration: none; margin-right: auto; }
.nav__brand img { width: 46px; height: auto; }
.nav__brandtx { display: flex; flex-direction: column; line-height: 1; }
.nav__brandtx b {
  font-family: Anton, sans-serif; font-weight: 400;
  font-size: 1.16rem; letter-spacing: .01em; text-transform: uppercase;
}
.nav__brandtx i {
  font-style: normal; font-size: .63rem; font-weight: 600;
  letter-spacing: .27em; text-transform: uppercase; color: var(--tx-3);
  margin-top: 2px;
}

.nav__links { display: none; gap: clamp(1rem, 2vw, 1.9rem); }
.nav__links a {
  position: relative; text-decoration: none; font-size: .93rem; font-weight: 600;
  color: var(--tx-2); padding-block: .4rem;
  transition: color .25s var(--ease);
}
.nav__links a::after {
  content: ''; position: absolute; left: 0; right: 0; bottom: 0; height: 2px;
  background: var(--flow); border-radius: 2px;
  transform: scaleX(0); transform-origin: left;
  transition: transform .35s var(--ease-out);
}
.nav__links a:hover, .nav__links a.is-active { color: var(--tx); }
.nav__links a:hover::after, .nav__links a.is-active::after { transform: scaleX(1); }

.nav__cta { display: none; }

/* Scroll progress, drawn in the brand's own colours. */
.nav__rail {
  position: absolute; left: 0; bottom: 0; height: 3px;
  width: 100%; display: flex;
  transform: scaleX(var(--progress, 0)); transform-origin: left;
  transition: transform .1s linear;
}
.nav__rail i { flex: 1; background: var(--c); }

.nav__burger {
  width: 44px; height: 44px; display: grid; place-content: center; gap: 5px;
  margin-right: -8px;
}
.nav__burger span {
  display: block; width: 22px; height: 2px; background: var(--tx); border-radius: 2px;
  transition: transform .3s var(--ease-out), opacity .2s var(--ease);
}
.nav__burger[aria-expanded="true"] span:nth-child(1) { transform: translateY(7px) rotate(45deg); }
.nav__burger[aria-expanded="true"] span:nth-child(2) { opacity: 0; }
.nav__burger[aria-expanded="true"] span:nth-child(3) { transform: translateY(-7px) rotate(-45deg); }

@media (min-width: 900px) {
  .nav__links { display: flex; }
  .nav__cta { display: inline-flex; }
  .nav__burger { display: none; }
}

.mmenu {
  position: fixed; inset: var(--nav-h) 0 0; z-index: 99;
  background: var(--bg);
  padding: var(--sp) var(--gut) calc(var(--sp) * 2);
  display: flex; flex-direction: column; gap: 2rem;
  overflow-y: auto;
}
.mmenu[hidden] { display: none; }
.mmenu nav { display: flex; flex-direction: column; }
.mmenu nav a {
  font-family: Anton, sans-serif; font-size: 2rem; text-transform: uppercase;
  text-decoration: none; padding: .55rem 0; border-bottom: 1px solid var(--line);
}
.mmenu__cta { display: flex; flex-direction: column; gap: .75rem; }

/* -------------------------------------------------------------------- hero */
.hero {
  position: relative;
  min-height: min(100svh, 900px);
  display: grid; align-items: end;
  padding-top: calc(var(--nav-h) + 2rem);
  padding-bottom: clamp(3rem, 7vw, 6rem);
  overflow: hidden;
}
.hero__bg { position: absolute; inset: 0; z-index: 0; }
/* <picture> is display:inline and would not fill the hero. */
.hero__bg picture { display: block; width: 100%; height: 100%; }
/* Colour is the DEFAULT state, not the reward. A signwriter's proof is colour,
   so every failure mode — no JS, slow font, throttled tab — must fail INTO it.
   The "colour flows in" moment is applied only when .has-js confirms a live
   script, and it is short enough that nobody ever sees a grey shopfront. */
.hero__bg img {
  width: 100%; height: 100%; object-fit: cover; object-position: 58% 42%;
  filter: saturate(1.05) contrast(1.06);
}
/* Gated on .is-lit, which app.js sets once the pixels have actually arrived.
   Ungated, the colour-flow played while the image was still downloading on
   4G, so the site's opening statement animated an empty box. */
.has-js .hero__bg img.is-lit {
  animation: heroColour 1.15s var(--ease-out) both;
}
@keyframes heroColour {
  from { filter: saturate(.45) contrast(1.04); transform: scale(1.045); }
  to   { filter: saturate(1.05) contrast(1.06); transform: scale(1); }
}
.hero__scrim {
  position: absolute; inset: 0;
  background:
    linear-gradient(180deg, #0B0B0FDD 0%, #0B0B0F55 26%, #0B0B0FCC 68%, #0B0B0F 100%),
    radial-gradient(120% 85% at 18% 88%, #0B0B0FEE 0%, transparent 62%);
}

.hero__in { position: relative; z-index: 1; width: min(100% - var(--gut) * 2, var(--max)); margin-inline: auto; }

.hero__eyebrow {
  display: flex; flex-wrap: wrap; align-items: center; gap: .55rem;
  font-size: .8rem; font-weight: 800; letter-spacing: .2em; text-transform: uppercase;
  margin-bottom: 1.4rem;
}
.hero__eyebrow span { color: color-mix(in srgb, var(--c) 62%, #fff); } /* PRINT was 4.26, INSTALL 4.63 over a variable scrim at 12.8px/800 */
.hero__eyebrow i { color: var(--brand-gold); font-style: normal; font-size: .7rem; }

.hero__h1 {
  font-family: Anton, Impact, sans-serif; font-weight: 400;
  font-size: clamp(3rem, 1.1rem + 9.6vw, 8.6rem);
  line-height: .87; letter-spacing: -.018em; text-transform: uppercase;
  margin-bottom: 1.1rem;
}
.hero__h1 span { display: block; }
/* Not a rainbow gradient clipped into the letterforms — that is the most
   over-used device on the web and it is nobody's brand. This is the layered
   hard drop-shadow a signwriter actually paints, straight off their own logo:
   solid fill, dark offset, colour offset behind it. */
.hero__h1 em {
  font-style: normal;
  color: var(--brand-pink);
  text-shadow:
     .022em .022em 0 #0B0B0F,
     .045em .045em 0 var(--brand-gold),
     .068em .068em 0 #0B0B0F;
  padding-right: .07em;
}

.hero__tag {
  font-size: clamp(1.05rem, .95rem + .5vw, 1.4rem);
  color: var(--brand-gold); font-weight: 500; font-style: italic;
  margin-bottom: .9rem;
}
.hero__sub { color: var(--tx-2); max-width: 52ch; margin-bottom: 2rem; }

.hero__cta { display: flex; flex-wrap: wrap; gap: .8rem; margin-bottom: 2rem; }

.hero__proof { font-size: .92rem; color: var(--tx-2); display: flex; flex-wrap: wrap; gap: .45rem; align-items: baseline; }
.hero__proof b { font-size: 1.35rem; color: var(--v4); font-family: Anton, sans-serif; font-weight: 400; }
.hero__proof span { color: var(--tx-3); }

.hero__scroll {
  position: absolute; left: 50%; bottom: 1.4rem; z-index: 2;
  translate: -50% 0;
  width: 26px; height: 42px; border: 2px solid var(--line-2); border-radius: 100px;
  display: none; place-content: center;
}
.hero__scroll span {
  display: block; width: 4px; height: 8px; border-radius: 3px; background: var(--tx-2);
  animation: scrollDot 1.9s var(--ease) infinite;
}
@keyframes scrollDot {
  0%, 100% { transform: translateY(-5px); opacity: 0; }
  40%      { transform: translateY(0);    opacity: 1; }
  75%      { transform: translateY(6px);  opacity: 0; }
}
@media (min-width: 900px) and (min-height: 700px) { .hero__scroll { display: grid; } }

/* --------------------------------------------------------------- squeegee */
.sq { background: var(--bg-2); }
.sq__in { display: grid; gap: clamp(2.2rem, 5vw, 4.5rem); align-items: center; }
@media (min-width: 940px) { .sq__in { grid-template-columns: .82fr 1.18fr; } }

.sq__hint {
  display: inline-flex; align-items: center; gap: .65rem;
  margin-top: 1.7rem; font-weight: 700; font-size: .93rem;
  color: var(--tx); letter-spacing: .04em; text-transform: uppercase;
}
.sq__hintico {
  width: 34px; height: 34px; border-radius: 50%;
  display: grid; place-content: center;
  background: var(--v3); color: #000;
  animation: nudge 2.6s var(--ease) infinite;
}
.sq__hintico svg { width: 17px; height: 17px; }
@keyframes nudge {
  0%, 62%, 100% { transform: translateX(0); }
  72%           { transform: translateX(6px); }
  82%           { transform: translateX(0); }
}
.sq__hint.is-done .sq__hintico { animation: none; background: var(--v4); }

.sq__stage { margin: 0; }
.sq__frame {
  position: relative; overflow: hidden;
  /* Without these the desktop drag dies on the first move: pressing and dragging
     over an <img> starts Chrome's native image drag-and-drop, which fires
     pointercancel and shows a ghost thumbnail. Touch has no equivalent gesture,
     which is why this only ever broke with a mouse. */
  user-select: none; -webkit-user-select: none;
  border-radius: var(--r-l);
  box-shadow: var(--sh-3);
  background: var(--bg-3);
  isolation: isolate;
  touch-action: pan-y;
  cursor: ew-resize;
}
.sq__img { width: 100%; height: auto; display: block; -webkit-user-drag: none; pointer-events: none; }
.sq__img--flat { filter: grayscale(1) brightness(.62) contrast(1.08); }

.sq__colour {
  position: absolute; inset: 0;
  clip-path: inset(0 calc(100% - var(--wipe, 38%)) 0 0);
  will-change: clip-path;
}
.sq__colour .sq__img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }

.sq__blade {
  position: absolute; top: 0; bottom: 0;
  left: var(--wipe, 38%);
  width: 0; z-index: 3;
  pointer-events: none;
}
.sq__bladebar {
  position: absolute; top: 0; bottom: 0; left: -2px; width: 4px;
  background: var(--flow);
  background-size: 4px 100%;
  box-shadow: 0 0 22px 3px #ffffff55;
}
.sq__grip {
  position: absolute; top: 50%; left: 0; translate: -50% -50%;
  width: 54px; height: 54px; border-radius: 50%;
  background: #fff; color: #0B0B0F;
  display: grid; place-content: center;
  box-shadow: 0 6px 20px rgb(0 0 0 / .55);
}
.sq__grip svg { width: 24px; height: 24px; }

/* The range input is the real control: keyboard, screen reader and touch all
   get the same thing. It sits over the frame, invisible but operable. */
.sq__range {
  position: absolute; inset: 0;
  width: 100%; height: 100%;
  /* The input stays for keyboard and screen-reader users — arrow keys drive it
     and it is still focusable — but it must NOT eat pointers. It is a sibling of
     .sq__frame positioned over it and later in DOM order, so it won hit-testing
     across the whole block, and iOS never gave the drag to anything. With
     pointer-events: none the frame's own handlers (app.js) run on every
     platform, and its touch-action: pan-y guard is finally reachable. */
  pointer-events: none;
  opacity: 0;
  -webkit-appearance: none; appearance: none; background: transparent;
  margin: 0;
}
.sq__stage { position: relative; }
.sq__range::-webkit-slider-thumb { -webkit-appearance: none; width: 56px; height: 100%; }
.sq__range::-moz-range-thumb { width: 56px; height: 999px; border: 0; opacity: 0; }
.sq__range:focus-visible { opacity: 1; outline: 3px solid var(--v3); outline-offset: 4px; }

.sq__cap { margin-top: .95rem; font-size: .86rem; color: var(--tx-3); }

/* ---------------------------------------------------------------- services */
.svc__grid {
  display: grid; gap: clamp(1rem, 2vw, 1.5rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 290px), 1fr));
}
.svc__card {
  position: relative; overflow: hidden;
  background: var(--bg-2);
  border: 1px solid var(--line);
  border-radius: var(--r-m);
  padding: clamp(1.5rem, 3vw, 2.1rem);
  transition: transform .4s var(--ease-out), border-color .4s var(--ease), background-color .4s var(--ease);
}
.svc__card:hover { transform: translateY(-5px); border-color: var(--c); background: var(--bg-3); }
.svc__spine {
  position: absolute; left: 0; top: 0; bottom: 0; width: 4px;
  background: var(--c);
  transform: scaleY(.24); transform-origin: top;
  transition: transform .5s var(--ease-out);
}
.svc__card:hover .svc__spine, .svc__card:focus-within .svc__spine { transform: scaleY(1); }

.svc__ico {
  display: grid; place-content: center;
  width: 50px; height: 50px; border-radius: 13px;
  background: color-mix(in srgb, var(--c) 17%, transparent);
  color: var(--c); margin-bottom: 1.2rem;
}
.svc__ico .ico { width: 25px; height: 25px; }

.svc__t { font-size: 1.22rem; font-weight: 700; letter-spacing: -.01em; margin-bottom: .5rem; }
/* Same problem, same fix as .card__cat: raw vinyl on a dark ground fails AA as
   small text (blue #0055B8 at 2.70, red #E8112D at 4.10). This one matters most
   of the six — below 640px .svc__body is hidden, so this line is the entire
   description of the service. Measured worst case after the mix: 6.14. */
.svc__lead { color: color-mix(in srgb, var(--c) 62%, #fff); font-weight: 600; font-size: .97rem; margin-bottom: .7rem; }
.svc__body { color: var(--tx-2); font-size: .96rem; margin-bottom: 1.15rem; }

.svc__list { display: flex; flex-wrap: wrap; gap: .4rem; }
.svc__list li {
  font-size: .8rem; color: var(--tx-2);
  padding: .3rem .68rem; border-radius: 100px;
  border: 1px solid var(--line-2);
}

/* -------------------------------------------------------------------- work */
.work__filters { display: flex; flex-wrap: wrap; gap: .55rem; margin-bottom: clamp(1.6rem, 3vw, 2.4rem); }

.work__grid {
  display: grid; gap: clamp(.7rem, 1.6vw, 1.1rem);
  grid-template-columns: repeat(2, 1fr);
  grid-auto-flow: dense;
}
@media (min-width: 760px)  { .work__grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1080px) { .work__grid { grid-template-columns: repeat(4, 1fr); } }

.card { transition: opacity .38s var(--ease), transform .38s var(--ease); }
.card.is-hidden { display: none; }
@media (min-width: 760px) {
  .card--big { grid-column: span 2; grid-row: span 2; }
}

.card__btn {
  display: block; width: 100%; text-align: left;
  border-radius: var(--r-m); overflow: hidden;
  background: var(--bg-2); border: 1px solid var(--line);
  position: relative;
  transition: border-color .35s var(--ease), transform .35s var(--ease-out);
}
.card__btn:hover { border-color: var(--c); transform: translateY(-4px); }

.card__media { display: block; position: relative; overflow: hidden; aspect-ratio: 1 / 1; }
.card--big .card__media { aspect-ratio: 1 / 1; }
.card__media img {
  width: 100%; height: 100%; object-fit: cover;
  transform: scale(1.03);
  transition: filter .8s var(--ease-out), transform .7s var(--ease-out);
}
/* Colourless until it enters the viewport — then the colour flows in.
   Scoped to .has-js so a scriptless page shows the work in full colour. */
.has-js .card__media img { filter: grayscale(1) brightness(.72); }
.has-js .card.is-in .card__media img { filter: grayscale(0) brightness(1); }
.card__btn:hover .card__media img { transform: scale(1.07); }

.card__media::after {
  content: ''; position: absolute; inset: auto 0 0 0; height: 4px;
  background: var(--c); transform: scaleX(0); transform-origin: left;
  transition: transform .45s var(--ease-out);
}
.card__btn:hover .card__media::after { transform: scaleX(1); }

.card__meta { display: block; padding: .85rem .95rem 1rem; }
/* The raw vinyl colours are saturated, and several of them (blue #0055B8 at 2.3,
   magenta #EC008C at 3.6, red #E8112D at 4.0) fail AA as small text on this dark
   ground. Mixing each toward white keeps the colour-coding legible as an identity
   cue while lifting every one of the seven past 4.5. Measured worst case after
   this change: 5.9. Size also goes up from .67rem, which was simply too small. */
.card__cat {
  display: inline-block; font-size: .72rem; font-weight: 800;
  letter-spacing: .14em; text-transform: uppercase;
  color: color-mix(in srgb, var(--c) 62%, #fff);
  margin-bottom: .35rem;
}
.card__client { display: block; font-weight: 700; font-size: .98rem; line-height: 1.3; }
.card__title  { display: block; font-size: .85rem; color: var(--tx-3); }

.work__empty { margin-top: 2rem; color: var(--tx-3); }

.work__more { display: none; margin-top: 1.3rem; }

/* Measured at 360px: the page ran to 16176px, about 20 screens. The two worst
   offenders were the services section (3486px) and the work grid (3464px).
   Inside each service card the prose paragraph alone was 124px, and it repeats
   what the one-line lead already says — so it goes on a phone and stays on
   desktop. The work grid opens at 8 tiles with the rest one tap away. */
@media (max-width: 639px) {
  .svc__body { display: none; }
  .work__grid.is-clamped .card.is-over { display: none; }
  .work__more { display: block; }
}

/* --------------------------------------------------------------- lightbox */
.lb {
  border: 0; padding: 0; max-width: 100vw; max-height: 100svh;
  width: 100%; height: 100%;
  background: #060608F2; color: var(--tx);
}
.lb::backdrop { background: #060608EE; }
.lb[open] { display: grid; place-items: center; }

.lb__fig { margin: 0; display: grid; gap: 1rem; place-items: center; padding: clamp(3.4rem, 8vw, 5rem) 1rem 1rem; max-width: min(1100px, 100%); }
.lb__fig img {
  max-width: 100%; max-height: 72svh; width: auto; object-fit: contain;
  border-radius: var(--r-m); box-shadow: var(--sh-3);
}
.lb__fig figcaption { text-align: center; display: grid; gap: .2rem; padding-bottom: 1rem; }
.lb__fig b { font-size: 1.1rem; }
.lb__fig span { color: var(--tx-2); font-size: .95rem; }
.lb__fig em { color: var(--tx-3); font-style: normal; font-size: .88rem; max-width: 60ch; }

.lb__x, .lb__nav {
  position: absolute; z-index: 2;
  width: 46px; height: 46px; border-radius: 50%;
  display: grid; place-content: center;
  background: #ffffff14; color: var(--tx);
  border: 1px solid var(--line-2);
  transition: background-color .25s var(--ease), transform .25s var(--ease-out);
}
.lb__x:hover, .lb__nav:hover { background: #ffffff26; transform: scale(1.06); }
.lb__x svg, .lb__nav svg { width: 21px; height: 21px; }
.lb__x { top: 1rem; right: 1rem; }
.lb__nav { top: 50%; translate: 0 -50%; }
.lb__nav--prev { left: .7rem; }
.lb__nav--next { right: .7rem; }
@media (min-width: 900px) { .lb__nav--prev { left: 1.6rem; } .lb__nav--next { right: 1.6rem; } }

/* ----------------------------------------------------------------- process */
.proc { background: var(--bg-2); }
.proc__list {
  display: grid; gap: clamp(1rem, 2vw, 1.4rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 245px), 1fr));
  counter-reset: step;
}
.proc__step {
  position: relative;
  padding: clamp(1.5rem, 3vw, 2rem) clamp(1.2rem, 2.4vw, 1.7rem);
  background: var(--bg-3);
  border-radius: var(--r-m);
  border-top: 4px solid var(--c);
}
.proc__n {
  display: block;
  font-family: Anton, sans-serif; font-weight: 400; font-size: 2.5rem; line-height: 1;
  color: var(--c); margin-bottom: .6rem;
}
.proc__t { font-size: 1.1rem; font-weight: 700; margin-bottom: .5rem; }
.proc__step p { color: var(--tx-2); font-size: .95rem; }

/* ----------------------------------------------------------------- clients */
.cl { padding-block: clamp(2.2rem, 4vw, 3.4rem); border-block: 1px solid var(--line); overflow: hidden; }
.cl__strip { -webkit-mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent);
             mask-image: linear-gradient(90deg, transparent, #000 8%, #000 92%, transparent); }
.cl__track {
  display: flex; align-items: center; gap: 1.6rem; width: max-content;
  animation: marquee 46s linear infinite;
}
.cl:hover .cl__track { animation-play-state: paused; }
@keyframes marquee { to { transform: translateX(-50%); } }
.cl__item {
  font-family: Anton, sans-serif; font-weight: 400;
  font-size: clamp(1.05rem, .9rem + .8vw, 1.7rem);
  text-transform: uppercase; letter-spacing: .01em;
  color: var(--tx-3); white-space: nowrap;
  transition: color .3s var(--ease);
}
.cl:hover .cl__item { color: var(--c); }
.cl__dot { color: var(--line-2); font-size: .5rem; font-style: normal; }

/* ----------------------------------------------------------------- reviews */
.stars { color: var(--brand-gold); letter-spacing: .06em; }

.rv__grid {
  display: grid; gap: clamp(.9rem, 2vw, 1.4rem);
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 270px), 1fr));
}
.rv__card {
  display: flex; flex-direction: column; gap: .9rem;
  padding: clamp(1.4rem, 3vw, 1.9rem);
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r-m);
  border-left: 4px solid var(--c);
  transition: transform .35s var(--ease-out), background-color .35s var(--ease);
}
.rv__card:hover { transform: translateY(-4px); background: var(--bg-3); }
.rv__stars { color: var(--brand-gold); font-size: 1.02rem; letter-spacing: .08em; }
.rv__q { font-size: .98rem; color: var(--tx); line-height: 1.65; flex: 1; }
.rv__by { display: flex; flex-wrap: wrap; align-items: baseline; gap: .5rem; font-size: .88rem; }
.rv__by b { font-weight: 700; }
.rv__by span { color: var(--tx-3); }
.rv__more { margin-top: clamp(1.6rem, 3vw, 2.4rem); }

/* ------------------------------------------------------------------ social */
.soc__in { display: grid; gap: clamp(2.4rem, 5vw, 4rem); align-items: center; }
@media (min-width: 940px) { .soc__in { grid-template-columns: 1fr auto; } }

.soc__links { display: flex; flex-wrap: wrap; gap: .7rem; margin-top: 1.8rem; }
.soc__links .btn svg { width: 18px; height: 18px; }

.soc__phone { justify-self: center; }

/* The phone shell. Live Facebook timeline sits in the screen. */
.phone {
  position: relative;
  width: min(348px, 90vw);
  padding: 11px;
  border-radius: 42px;
  background: linear-gradient(160deg, #2A2A34, #131319 55%, #24242E);
  box-shadow:
    var(--sh-3),
    inset 0 0 0 1.5px #3A3A46,
    inset 0 2px 3px #ffffff18;
}
.phone::before {
  content: ''; position: absolute; inset: -3px; border-radius: 45px;
  background: var(--flow); opacity: .28; z-index: -1; filter: blur(11px);
}
.phone__notch {
  position: absolute; top: 19px; left: 50%; translate: -50% 0;
  width: 88px; height: 22px; border-radius: 100px; background: #0B0B0F; z-index: 3;
}
.phone__screen {
  position: relative;
  border-radius: 32px; overflow: hidden;
  background: #fff;
  /* A real handset proportion rather than the arbitrary 340/660 this used to
     carry. app.js measures the resulting box and asks Facebook for an embed of
     exactly that size, so the plugin no longer renders 360px of timeline inside
     a ~316px screen and get clipped down the right edge. */
  aspect-ratio: 9 / 18.5;
  /* A real status-bar strip. The island used to float over the top of the feed
     and sat squarely on the page name and profile photo. Now it has its own
     space and covers nothing. */
  padding-top: 36px;
  background: #fff;
}
.phone__screen iframe { width: 100%; height: 100%; border: 0; }

/* On a touch screen the embed used to eat the vertical flick: the finger landed
   on Facebook's own scroller and the page underneath stopped moving. The iframe
   is made inert and a real link covers it instead, so a flick always scrolls the
   page and a tap goes to the actual Facebook page, where browsing works better
   anyway. Mouse and trackpad keep the live, scrollable embed. */
.phone__tap { display: none; }
@media (pointer: coarse) {
  .phone__screen iframe { pointer-events: none; }
  .phone__tap {
    position: absolute; inset: 0; z-index: 4;
    display: flex; align-items: flex-end; justify-content: center;
    padding-bottom: 16px; text-decoration: none;
    background: linear-gradient(180deg, transparent 55%, rgb(0 0 0 / .58));
  }
  .phone__tap span {
    display: inline-flex; align-items: center; gap: .4rem;
    padding: .6rem 1.05rem; border-radius: 100px;
    background: var(--v7); color: #fff;
    font-size: .84rem; font-weight: 700;
    box-shadow: 0 6px 18px rgb(0 0 0 / .45);
  }
}

.phone__fallback {
  position: absolute; inset: 0; z-index: 5;
  display: grid; place-content: center; justify-items: center; gap: .55rem;
  padding: 1.6rem; text-align: center;
  background: var(--bg-2); color: var(--tx);
}
.phone__fallback[hidden] { display: none; }
.phone__fallback img { width: 116px; height: auto; margin-bottom: .4rem; }
.phone__fbnote { color: var(--tx-3); font-size: .84rem; margin-bottom: .6rem; }

.soc__cap { margin-top: 1.1rem; text-align: center; font-size: .9rem; color: var(--tx-2); }
.soc__cap b { color: var(--v4); font-family: Anton, sans-serif; font-weight: 400; font-size: 1.15rem; }

/* ------------------------------------------------------------------- about */
.ab { background: var(--bg-2); }
.ab__in { display: grid; gap: clamp(2.2rem, 5vw, 4rem); align-items: center; }
@media (min-width: 940px) { .ab__in { grid-template-columns: 1.05fr .95fr; } }

.ab__shot { margin: 0; }
.ab__shot img { width: 100%; border-radius: var(--r-l); box-shadow: var(--sh-3); }
.ab__shot figcaption { margin-top: .8rem; font-size: .86rem; color: var(--tx-3); }
.ab__copy p + p { margin-top: 1rem; color: var(--tx-2); }

.ab__area { margin-top: 2.1rem; }
.ab__area h3 {
  font-size: .78rem; font-weight: 800; letter-spacing: .18em;
  text-transform: uppercase; color: var(--tx-3); margin-bottom: .85rem;
}
.ab__area ul { display: flex; flex-wrap: wrap; gap: .45rem; }
.ab__area li {
  font-size: .85rem; padding: .32rem .8rem; border-radius: 100px;
  border: 1px solid var(--line-2); color: var(--tx-2);
  transition: color .25s var(--ease), border-color .25s var(--ease);
}
.ab__area li:hover { color: var(--c); border-color: var(--c); }

/* ----------------------------------------------------------------- contact */
.ct__in { display: grid; gap: clamp(2.4rem, 5vw, 4.5rem); align-items: start; }
@media (min-width: 940px) { .ct__in { grid-template-columns: 1fr 1fr; } }

.ct__direct { margin-top: 2.2rem; display: grid; gap: .7rem; }
.ct__direct a {
  display: flex; align-items: center; gap: 1rem;
  padding: .95rem 1.1rem; border-radius: var(--r-m);
  background: var(--bg-2); border: 1px solid var(--line);
  text-decoration: none;
  transition: border-color .3s var(--ease), transform .3s var(--ease-out), background-color .3s var(--ease);
}
.ct__direct a:hover { border-color: var(--line-2); transform: translateX(4px); background: var(--bg-3); }
.ct__ico {
  width: 42px; height: 42px; flex: none; border-radius: 12px;
  display: grid; place-content: center;
  background: color-mix(in srgb, var(--c) 17%, transparent); color: var(--c);
}
.ct__ico svg { width: 21px; height: 21px; }
.ct__direct b { display: block; font-size: 1rem; }
.ct__direct em { display: block; font-style: normal; font-size: .84rem; color: var(--tx-3); }

.ct__form {
  background: var(--bg-2); border: 1px solid var(--line);
  border-radius: var(--r-l); padding: clamp(1.4rem, 3.4vw, 2.3rem);
  display: grid; gap: 1.05rem;
}
.fld { display: grid; gap: .4rem; }
.fld--two { grid-template-columns: 1fr; gap: 1.05rem; }
@media (min-width: 560px) { .fld--two { grid-template-columns: 1fr 1fr; } }
.fld--two > div { display: grid; gap: .4rem; }

.fld label { font-size: .86rem; font-weight: 600; color: var(--tx-2); }
.fld label span { color: var(--tx-3); font-weight: 400; }

.fld input, .fld select, .fld textarea {
  width: 100%; padding: .8rem .95rem;
  background: var(--bg); color: var(--tx);
  border: 1.5px solid var(--line-3); border-radius: var(--r-s);
  transition: border-color .25s var(--ease), box-shadow .25s var(--ease);
}
.fld textarea { resize: vertical; min-height: 110px; }
.fld input:focus, .fld select:focus, .fld textarea:focus {
  outline: none; border-color: var(--v5);
  box-shadow: 0 0 0 3px color-mix(in srgb, var(--v5) 26%, transparent);
}
.fld input[aria-invalid="true"], .fld textarea[aria-invalid="true"] { border-color: var(--v1); }

.fld__err { font-size: .82rem; color: #FF8A96; min-height: 0; }
.fld__err:empty { display: none; }

.hp { position: absolute; left: -9999px; width: 1px; height: 1px; overflow: hidden; }

.ct__note { font-size: .86rem; color: var(--tx-3); text-align: center; }
.ct__status { font-size: .92rem; font-weight: 600; text-align: center; }
.ct__status:empty { display: none; }
.ct__status.is-ok  { color: var(--v4); }
.ct__status.is-bad { color: #FF8A96; }

/* ------------------------------------------------------------------ footer */
.ft { position: relative; background: var(--bg-2); border-top: 1px solid var(--line); padding-top: clamp(3rem, 6vw, 5rem); }
.ft__in { display: grid; gap: clamp(2rem, 4vw, 3.5rem); padding-bottom: clamp(2.4rem, 5vw, 3.5rem); }
@media (min-width: 800px) { .ft__in { grid-template-columns: 1fr 1.25fr; align-items: start; } }

.ft__brand img { width: min(300px, 74vw); height: auto; }
.ft__tag { margin-top: .9rem; color: var(--brand-gold); font-style: italic; font-size: .98rem; }

.ft__cols { display: grid; gap: 1.8rem; grid-template-columns: repeat(auto-fit, minmax(140px, 1fr)); }
.ft__cols h3 {
  font-size: .74rem; font-weight: 800; letter-spacing: .18em;
  text-transform: uppercase; color: var(--tx-3); margin-bottom: .7rem;
}
.ft__cols p { color: var(--tx-2); font-size: .95rem; line-height: 1.85; }
.ft__cols a { text-decoration: none; transition: color .25s var(--ease); }
.ft__cols a:hover { color: var(--v5); }

/* Footer links measured 17px tall on a phone — the phone number and email among
   them, which is exactly what a one-handed visitor reaches for. On touch devices
   give every one of them a full 44px target. Scoped to pointer:coarse so the
   desktop footer keeps its tighter rhythm. */
@media (pointer: coarse) {
  .ft__cols a { display: inline-flex; align-items: center; min-height: 44px; }
  .ft__cols p { line-height: 1.5; }
}
.ft__fax { color: var(--tx-3); font-size: .88rem; }

.ft__rail { display: flex; height: 5px; }
.ft__rail i { flex: 1; background: var(--c); }

.ft__base {
  display: flex; flex-wrap: wrap; gap: .6rem; justify-content: space-between;
  padding-block: 1.3rem; font-size: .84rem; color: var(--tx-3);
}
.ft__mini { letter-spacing: .12em; text-transform: uppercase; font-size: .74rem; }

/* The SerApps wordmark is dark navy, which would disappear on this ground, so
   it sits on a light chip rather than being filtered to a flat white and losing
   the cyan palm that makes it theirs. */
.ft__by {
  display: inline-flex; align-items: center; gap: .8rem;
  padding: .7rem 1.1rem .7rem .95rem;
  border-radius: 100px;
  background: #F4F6F8;
  text-decoration: none;
  box-shadow: var(--sh-1);
  transition: transform .3s var(--ease-out), box-shadow .3s var(--ease-out);
}
.ft__by span {
  font-size: .78rem; font-weight: 700; letter-spacing: .1em;
  text-transform: uppercase; color: #5B6472;
  white-space: nowrap;
}
.ft__by img { width: 104px; height: auto; }
.ft__by:hover { transform: translateY(-2px); box-shadow: var(--sh-2); }
@media (max-width: 480px) {
  .ft__by { gap: .6rem; padding: .6rem .9rem; }
  .ft__by span { font-size: .68rem; letter-spacing: .07em; }
  .ft__by img { width: 88px; }
}

/* --------------------------------------------------------------------- fab */
/* Mobile-only call button. The whole point of the site is this tap. */
.fab {
  position: fixed; right: 1rem; bottom: 1rem; z-index: 90;
  display: inline-flex; align-items: center; gap: .5rem;
  padding: .85rem 1.25rem; border-radius: 100px;
  background: var(--v7); color: #fff; text-decoration: none;
  font-weight: 700; box-shadow: 0 8px 26px rgb(0 0 0 / .55);
  transform: translateY(140%);
  transition: transform .45s var(--ease-out);
}
.fab svg { width: 19px; height: 19px; }
.fab.is-on { transform: translateY(0); }
@media (min-width: 900px) { .fab { display: none; } }

/* --------------------------------------------------------- reveal on scroll */
/* The hidden start state is scoped to .has-js, which app.js sets on <html> as
   its very first act. If the script never runs — blocked, errored, disabled —
   nothing is ever hidden, so the page is fully readable either way. */
.has-js [data-reveal] {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .75s var(--ease-out), transform .75s var(--ease-out);
  transition-delay: var(--d, 0ms);
}
.has-js [data-reveal].is-in { opacity: 1; transform: none; }

/* ------------------------------------------------------- reduced motion */
@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: .001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: .001ms !important;
  }
  .hero__bg img { filter: saturate(1.05) contrast(1.06); transform: none; animation: none; }
  .cl__track { animation: none; transform: none; }
  [data-reveal] { opacity: 1; transform: none; }
  /* Colour is information here, not decoration — show it without the fade. */
  .card__media img { filter: none; }
  .fab { transform: none; }
}
