/* ===========================================================================
   Right 1 Group — concept 3, "Projects".
   Its OWN design system. Nothing here is shared with concepts 1 or 2, and
   nothing is lifted from another client's stylesheet.

   WHY THIS LOOKS THE WAY IT DOES
   Imran, on the call of 27 Aug 2026: he wants one Right 1 Group brand with the
   divisions living inside it rather than four brands of equal size, electrical
   leading and security second, a landing page for EV, and a homepage carrying
   "a slide show of videos of projects continuously going through or photos"
   so the page reads project led and professional rather than, in his words, a
   generic service page. He also wants the site security poles shown as
   products with prices, because he does not advertise that side at all today.

   So the ground of this concept is PHOTOGRAPHY, not a colour field. Full bleed
   project frames carry the page, dark scrims carry the type, and the cyan is
   held back. That is deliberate, and it is the single biggest thing separating
   this direction from concept 1: colour carries a verdict here, not decoration
   (SDT, 19 Aug 2026). The accent appears on the live rail, the active state,
   a valid field and the submitted state, and almost nowhere else.

   DIFFERENTIATION NOTE. 22 Electrical Works is another Melbourne electrician
   we build for and their site is also blue on near black, so per the Glow
   lesson the separation has to be in TEMPERATURE and LAYOUT LANGUAGE, not in
   swapping a hue. Theirs is a cold blue black ground with 16px pill cards that
   lift and glow. This one is a neutral graphite ground, 10px radius, hairline
   rules, and a numbered project index instead of a card grid. Right 1 Group's
   own cyan is kept exactly as sampled from their logo, because their real
   brand always wins.

   TYPE. Supreme over Switzer, both Fontshare. Was Clash Display until
   28 Aug 2026; see the note on --disp for why it changed. Deliberately not
   Inter, Manrope or Poppins: those read as generated, and Manrope and Poppins
   are already 22 Electrical Works.
   =========================================================================== */

:root {
  /* sampled from logo-horizontal-light.png — their real cyan, unchanged */
  --cy: #00ADEF;
  --cy-lt: #57D2FF;       /* same hue lifted, 11.0:1 on --ink, safe as text */
  --cy-dp: #0072A0;       /* same hue darkened, 5.2:1 on white */
  --cy-glow: rgba(0,173,239,.34);

  /* a NEUTRAL graphite, not a blue black. This is the temperature move that
     separates the page from 22 Electrical Works at a glance. */
  --ink: #0A0B0C;
  --ink-2: #121416;
  --ink-3: #1A1D20;
  --ink-4: #24282C;

  --paper: #FFFFFF;
  --txt: #ECEEF0;         /* 16.9:1 on --ink */
  --mut: #9CA4AC;         /* 7.2:1 on --ink */
  /* The quiet text tier. It was #6F777F, which is 4.33:1 on --ink and 4.06:1
     on the lifted panel, and the audit correctly failed it everywhere it was
     doing real work: the rail numbers, the project index numbers and every
     project sub line. Lifted to clear 4.5:1 on BOTH grounds rather than only
     the darker one, because half of these sit on --ink-2. */
  --mut-2: #8A929A;       /* 6.25:1 on --ink, 5.85:1 on --ink-2 */
  --mut-3: #6F777F;       /* the old value. Rules and dividers only, no text */
  --on-cy: #04222E;       /* dark text for use ON the cyan fill, 9.1:1 */

  --line: rgba(255,255,255,.10);
  --line-2: rgba(255,255,255,.18);

  /* Supreme replaced Clash Display on 28 Aug 2026. Tim: "change the main font,
     make it simpler." Clash Display is a wide, characterful display face and
     it was doing too much work at hero size, and because Fontshare ships no
     italic for it every <em> in a heading came out as a synthetic oblique,
     which is the slant that reads as a design flourish rather than a decision.
     Supreme is a plain squared grotesk, still Fontshare, still nothing like
     the template faces, and it takes a real 700 rather than a 600. */
  --disp: 'Supreme', system-ui, sans-serif;
  --body: 'Switzer', system-ui, -apple-system, 'Segoe UI', sans-serif;

  --wrap: 1240px;
  --pad: 24px;           /* the wrap gutter. Anything that bleeds past the
                            column must be tied to this, never hard coded. */
  --r: 10px;              /* tight. 22EW is 16px pills; this is a spec sheet */
  --ease: cubic-bezier(.22,1,.36,1);
  --hd-h: 74px;
}

/* ---------- reset ---------- */
*, *::before, *::after { box-sizing: border-box; }
html { -webkit-text-size-adjust: 100%; }
body {
  margin: 0; background: var(--ink); color: var(--txt);
  font-family: var(--body); font-size: 17px; line-height: 1.62;
  font-weight: 400; letter-spacing: -.004em;
  -webkit-font-smoothing: antialiased; overflow-x: hidden;
}
img { max-width: 100%; display: block; height: auto; }
/* height:auto is load bearing — the width/height attributes on an <img> map to
   a presentational height hint, and without this any aspect-ratio we set is
   silently ignored and the image renders at its natural pixel height. */

a { color: inherit; }
button { font: inherit; }

h1, h2, h3, h4 {
  font-family: var(--disp); font-weight: 700; line-height: 1.02;
  letter-spacing: -.022em; margin: 0;
}
h1 { font-size: clamp(2.6rem, 6.4vw, 4.6rem); }
h2 { font-size: clamp(2rem, 4.2vw, 3.15rem); }
h3 { font-size: clamp(1.15rem, 1.9vw, 1.4rem); letter-spacing: -.014em; }
p { margin: 0 0 1.05em; }
p:last-child { margin-bottom: 0; }

.wrap { width: 100%; max-width: var(--wrap); margin: 0 auto; padding: 0 var(--pad); }

/* anchors have to clear the sticky header on their own, because Lenis carries
   the only scroll offset and Lenis is desktop only */
section[id] { scroll-margin-top: calc(var(--hd-h) + 18px); }

.eyebrow {
  font-family: var(--body); font-size: .74rem; font-weight: 600;
  letter-spacing: .18em; text-transform: uppercase; color: var(--cy-lt);
  margin: 0 0 16px;
}
.lede { font-size: clamp(1.02rem, 1.5vw, 1.16rem); color: var(--mut); max-width: 62ch; }

/* ---------- buttons ---------- */
.btn {
  display: inline-flex; align-items: center; justify-content: center; gap: 9px;
  min-height: 52px; padding: 0 26px; border-radius: var(--r);
  font-family: var(--body); font-size: .96rem; font-weight: 600;
  letter-spacing: -.008em; text-decoration: none; cursor: pointer;
  border: 1px solid transparent; transition: background .22s, border-color .22s,
    color .22s, transform .16s var(--ease);
}
.btn:active { transform: translateY(1px); }
.btn-pri { background: var(--cy); color: var(--on-cy); border-color: var(--cy); }
.btn-pri:hover { background: var(--cy-lt); border-color: var(--cy-lt); }
.btn-gho { background: rgba(255,255,255,.04); color: var(--txt); border-color: var(--line-2); }
.btn-gho:hover { background: rgba(255,255,255,.09); border-color: var(--txt); }
.btn-sm { min-height: 44px; padding: 0 18px; font-size: .89rem; }
.btn:focus-visible { outline: 2px solid var(--cy-lt); outline-offset: 3px; }

/* ---------- scroll progress ---------- */
.prog {
  position: fixed; top: 0; left: 0; height: 2px; width: 100%; z-index: 90;
  transform-origin: 0 50%; transform: scaleX(0);
  background: linear-gradient(90deg, var(--cy), var(--cy-lt));
  pointer-events: none;
}

/* ---------- header ---------- */
.hd {
  position: fixed; top: 0; left: 0; right: 0; z-index: 80; height: var(--hd-h);
  display: flex; align-items: center;
  background: linear-gradient(180deg, rgba(10,11,12,.86), rgba(10,11,12,0));
  transition: background .3s, backdrop-filter .3s, border-color .3s, transform .4s var(--ease);
  border-bottom: 1px solid transparent;
}
.hd.solid {
  background: rgba(10,11,12,.82);
  -webkit-backdrop-filter: blur(20px) saturate(170%);
  backdrop-filter: blur(20px) saturate(170%);
  border-bottom-color: var(--line);
}
/* chrome on demand, the scalus.app move: the top bar steps aside on a scroll
   down so the dock owns the screen, and comes back the moment you scroll up */
.hd.tuck { transform: translateY(-104%); }
.hd-in { display: flex; align-items: center; gap: 20px; width: 100%; }
/* the logo is 34px of artwork, so the LINK carries padding to reach a 44px
   hit area. A logo link is a real tap target and it fails the audit otherwise */
.brand { display: flex; align-items: center; flex: none; text-decoration: none;
  min-height: 46px; padding: 6px 4px; margin-left: -4px; }
.brand img { height: 34px; width: auto; }
.nav { display: flex; align-items: center; gap: 4px; margin-left: auto; }
.nav a {
  position: relative; padding: 11px 13px; border-radius: 8px;
  font-size: .91rem; font-weight: 500; color: var(--mut);
  text-decoration: none; transition: color .2s, background .2s;
}
.nav a:hover { color: var(--txt); background: rgba(255,255,255,.06); }
.hd-cta { flex: none; }
.hd-tel { display: none; }

.burger {
  display: none; width: 46px; height: 46px; flex: none; margin-left: auto;
  align-items: center; justify-content: center; cursor: pointer;
  background: rgba(255,255,255,.05); border: 1px solid var(--line-2);
  border-radius: 9px; padding: 0;
}
.burger span, .burger span::before, .burger span::after {
  content: ''; display: block; width: 19px; height: 2px; border-radius: 2px;
  background: var(--txt); transition: transform .28s var(--ease), opacity .2s;
}
.burger span::before { position: absolute; transform: translateY(-6px); }
.burger span::after { position: absolute; transform: translateY(6px); }
.burger span { position: relative; }
.burger[aria-expanded="true"] span { background: transparent; }
.burger[aria-expanded="true"] span::before { transform: rotate(45deg); }
.burger[aria-expanded="true"] span::after { transform: rotate(-45deg); }

.mnav {
  position: fixed; left: 0; right: 0; top: var(--hd-h); z-index: 79;
  display: flex; flex-direction: column; padding: 10px 16px 22px;
  background: rgba(10,11,12,.97);
  -webkit-backdrop-filter: blur(24px); backdrop-filter: blur(24px);
  border-bottom: 1px solid var(--line);
  transform: translateY(-14px); opacity: 0; pointer-events: none;
  transition: transform .32s var(--ease), opacity .26s;
  max-height: calc(100vh - var(--hd-h)); overflow-y: auto;
}
.mnav.on { transform: none; opacity: 1; pointer-events: auto; }
.mnav a {
  display: flex; align-items: center; min-height: 54px; padding: 0 8px;
  font-family: var(--disp); font-size: 1.22rem; font-weight: 700;
  color: var(--txt); text-decoration: none;
  border-bottom: 1px solid var(--line);
}
.mnav a:last-of-type { border-bottom: 0; }
.mnav .btn { margin-top: 14px; width: 100%; }

/* ===========================================================================
   HERO — the rolling project slideshow.
   This is the thing Imran actually asked for, so it is the whole first screen
   rather than a strip inside it. Frames cross fade on a slow Ken Burns push,
   a numbered rail underneath says which frame you are on and lets you jump,
   and the caption changes with the frame.

   The frames sit in a stack with only opacity animating. Do NOT animate the
   transform of the wrapper: a transformed ancestor kills backdrop-filter on
   its children in Chrome and Safari, which would silently flatten the glass
   quiz panel sitting over the top of it into a grey box.
   =========================================================================== */
/* Tim, 28 Aug: "make sure there is space on the bottom so centre it all out to
   be in the middle vertically". flex-end pinned the whole block to the floor of
   the hero, which stacked every pixel of slack above the headline and left the
   card almost touching the bottom edge. Centred, the slack splits evenly and
   the composition sits in the middle of the frame with room under it. The top
   padding still clears the fixed header, so centring cannot slide the headline
   up behind the nav. */
.hero { position: relative; min-height: 100svh; display: flex; align-items: center;
  padding: calc(var(--hd-h) + 40px) 0 56px; overflow: hidden; isolation: isolate; }

.slides { position: absolute; inset: 0; z-index: 0; background: var(--ink-2); }
/* overflow:hidden HERE, on the frame itself, not only on the hero. The Ken
   Burns push scales the img past its box, and relying on an ancestor to clip
   it left the document 6px wider than the viewport on a phone. Nothing was
   visibly wrong and no unclipped element could be found, because the leak is
   the scaled box contributing to scrollWidth rather than anything painting
   outside. Clip it at the frame and the number is exact. */
.slide {
  position: absolute; inset: 0; opacity: 0; overflow: hidden;
  transition: opacity 1.25s ease;
}
.slide.on { opacity: 1; }
.slide img {
  width: 100%; height: 100%; object-fit: cover;
  transform: scale(1.06);
}
.slide.on img { animation: kb 9s linear forwards; }
@keyframes kb { from { transform: scale(1.06); } to { transform: scale(1.14); } }

/* the scrim. Two layers, because one flat overlay either washes the photo out
   or leaves the type unreadable at the bottom where the copy actually is. */
.slides::after {
  content: ''; position: absolute; inset: 0; z-index: 1; pointer-events: none;
  background:
    linear-gradient(180deg, rgba(10,11,12,.80) 0%, rgba(10,11,12,.30) 34%, rgba(10,11,12,.86) 82%, rgba(10,11,12,.97) 100%),
    linear-gradient(94deg, rgba(10,11,12,.86) 0%, rgba(10,11,12,.42) 46%, rgba(10,11,12,.12) 100%);
}
.hero > .wrap { position: relative; z-index: 2; width: 100%; }

/* Tim, 28 Aug: "line up the form to the text". align-items:end bottom aligned
   the two columns, so the card's top edge floated ~140px below the headline
   and the pair read as two separate objects rather than one composition. Top
   aligned, the card's top edge and the headline's cap height start together. */
.hero-grid {
  display: grid; grid-template-columns: minmax(0,1fr) 436px; gap: 28px;
  align-items: start;
}
/* Optical alignment, not box alignment. The two columns already share a top
   edge at 412px, but an h1 line box carries its leading ABOVE the cap height,
   so the letters start ~15px lower than the box does and the card reads as
   floating higher than the headline it is supposed to line up with. Nudging
   the card down by that leading is what actually makes the two look level. */
.hero-grid > .hq { margin-top: 15px; }
/* Tim, 28 Aug: "make the text spread out more horizontally so it fills the
   space", then "make the form and the heading proper". Two separate limits
   were holding it back. 30ch capped the COLUMN, and the global h1 clamp capped
   the TYPE at 4.6rem, so even released the headline only covered about 590px
   of a 728px column and left a canyon between the text and the card. Lifting
   the ceiling to 5.6rem lets the line reach the card instead of stopping short
   of it, which is what actually closes the gap between the two. */
.hero-copy { max-width: none; }
.hh { font-size: clamp(2.6rem, 6vw, 5.6rem); }
/* Tim, 28 Aug: "make Right1 animate gradient blue shine shimmer" — same
   moving gradient as the card's logo lockup (.r3-lk-logo, r3LogoShine),
   clipped to the text instead of a mask so the two shimmer in step. */
.hh em {
  font-style: normal; white-space: nowrap;
  background-image: linear-gradient(100deg,
    var(--cy-lt) 0%, var(--cy) 20%, #EAF9FF 36%, var(--cy) 52%, var(--cy-dp) 72%, var(--cy-lt) 100%);
  background-size: 260% 100%;
  -webkit-background-clip: text; background-clip: text; color: transparent;
  animation: r3LogoShine 5s linear infinite;
}
.hero-sub { margin-top: 20px; font-size: clamp(1rem, 1.45vw, 1.14rem); color: var(--txt);
  opacity: .88; max-width: 46ch; }
.hero-btns { display: flex; flex-wrap: wrap; gap: 12px; margin-top: 28px; }

/* The slot the hero quiz takes over. It has to be a REAL box, not an empty
   div: the shared bundle finds the hero's right hand visual by MEASURING, and
   it ignores anything under 80px wide, under 150px tall, narrower than a fifth
   of the hero, or whose centre sits left of 52 per cent. An empty div fails
   every one of those and the bundle falls through to its restructuring path,
   which would pull this hero's own grid apart. */
.hero-slot { min-height: 470px; width: 100%; align-self: end; }

/* The caption that changes with the frame. The six of them are STACKED on top
   of each other rather than left in normal flow: only one is ever visible, and
   in flow the five hidden ones still occupy their full height and open a 200px
   hole between the caption and the rail. Opacity alone does not remove a box
   from the layout. */
.cap { position: relative; margin-top: 30px; min-height: 58px; }
.cap-in {
  position: absolute; inset: 0; opacity: 0; transform: translateY(8px);
  pointer-events: none;
  transition: opacity .5s, transform .5s var(--ease);
}
.cap-in.on { opacity: 1; transform: none; }
.cap-t { font-family: var(--disp); font-size: 1.02rem; font-weight: 700; color: var(--txt); }
.cap-m { font-size: .84rem; color: var(--mut); margin-top: 3px; }

/* the numbered rail. Reads as a project index, which is the point: it tells
   you at a glance that there are ten jobs behind the page, not one photo. */
.rail { display: flex; align-items: center; gap: 0; margin-top: 26px;
  border-top: 1px solid var(--line); padding-top: 14px; flex-wrap: wrap; }
.rail-b {
  position: relative; flex: 1 1 0; min-width: 26px; height: 44px; padding: 0;
  display: flex; align-items: flex-start; justify-content: flex-start;
  background: none; border: 0; cursor: pointer; color: var(--mut-2);
  font-size: .7rem; font-weight: 600; letter-spacing: .04em;
  transition: color .25s;
}
.rail-b::after {
  content: ''; position: absolute; top: -15px; left: 0; right: 2px; height: 2px;
  background: rgba(255,255,255,.12); border-radius: 2px;
}
.rail-b .fill {
  position: absolute; top: -15px; left: 0; height: 2px; width: 0; border-radius: 2px;
  background: var(--cy); box-shadow: 0 0 10px var(--cy-glow);
}
.rail-b.on { color: var(--txt); }
.rail-b.on .fill { width: calc(100% - 2px); transition: width 9s linear; }
.rail-b.done .fill { width: calc(100% - 2px); background: rgba(255,255,255,.28); box-shadow: none; }
.rail-b:focus-visible { outline: 2px solid var(--cy-lt); outline-offset: 2px; }

/* ---------- sections ---------- */
.sec { padding: clamp(76px, 9vw, 122px) 0; position: relative; }
/* Tim, 28 Aug: "see how this is centered". Section headers sit on the centre
   line above whatever they introduce, rather than hard left against a two
   column body. The hero h1 is deliberately NOT part of this: it shares a row
   with the quiz card and has to stay on the same left edge as the card. */
.sec-hd { max-width: 64ch; margin: 0 auto 46px; text-align: center; }
.sec-hd .lede { margin-left: auto; margin-right: auto; }
/* Every <em> inside a heading, not only the ones that happen to sit in a
   .sec-hd. The security and EV headings are bare h2s, so their <em> was
   falling through to a real italic — and since neither Clash Display nor
   Supreme ships one, the browser synthesised a slant. That fake oblique is
   exactly the flourish Tim called out on 28 Aug. The accent is the colour,
   never a slant. */
h1 em, h2 em, h3 em { font-style: normal; color: var(--cy); }

/* ===========================================================================
   THE ONE BRAND BAND.
   Imran: "it's basically one company with multiple groups under it", and it is
   all going to "work under the one that I have, like Right 1 Group". So this
   is a single lockup with the divisions listed under it as weighted rows, NOT
   four equal tiles. The weighting is his: electrical first and largest,
   security second, EV as its own destination, air conditioning present but
   quiet, because he said he does it and does not prioritise it right now.
   =========================================================================== */
.one { border-top: 1px solid var(--line); }
.divs { display: grid; gap: 0; margin-top: 8px; }
.dv {
  position: relative; display: grid; grid-template-columns: 58px minmax(0,1fr) auto;
  align-items: center; gap: 22px; padding: 26px 0;
  border-bottom: 1px solid var(--line); text-decoration: none; color: inherit;
  transition: background .25s;
}
.dv:first-child { border-top: 1px solid var(--line); }
/* The hover wash bleeds out to the wrap's own gutter so the highlight reaches
   the edge of the column rather than stopping short. It has to be tied to
   --pad, NOT hard coded: at 24px it overhangs the 18px mobile gutter by 6px on
   each side and pushes documentElement.scrollWidth past the viewport. Nothing
   painted outside and no unclipped element could be found, because the element
   is invisible at rest; only hiding blocks one at a time found it. */
.dv::before {
  content: ''; position: absolute; left: calc(var(--pad) * -1); right: calc(var(--pad) * -1);
  top: 0; bottom: 0;
  background: rgba(255,255,255,.03); opacity: 0; border-radius: var(--r);
  transition: opacity .25s; z-index: 0;
}
.dv:hover::before { opacity: 1; }
.dv > * { position: relative; z-index: 1; }
.dv-n { font-family: var(--disp); font-size: .84rem; font-weight: 700;
  color: var(--mut-2); letter-spacing: .06em; }
.dv-b h3 { margin-bottom: 5px; }
.dv-b p { font-size: .93rem; color: var(--mut); margin: 0; max-width: 58ch; }
.dv-go { display: flex; align-items: center; gap: 8px; font-size: .86rem;
  font-weight: 600; color: var(--cy-lt); white-space: nowrap; }
.dv-go svg { width: 15px; height: 15px; transition: transform .25s var(--ease); }
.dv:hover .dv-go svg { transform: translateX(4px); }
/* the lead division gets the size, which is the whole hierarchy argument */
.dv.lead { padding: 34px 0; }
.dv.lead h3 { font-size: clamp(1.5rem, 2.6vw, 2rem); }
.dv.quiet h3 { font-size: 1.06rem; color: var(--mut); }
.dv.quiet .dv-b p { color: var(--mut-2); }

/* ===========================================================================
   PROJECT INDEX. A numbered list of jobs, not a three card row. The vibecoded
   look checklist calls out "three feature cards in a row" by name, and a
   builder judging whether we can do a four million dollar fit out wants to see
   a body of work, so the layout is an index with a large sticky frame beside
   it on desktop.
   =========================================================================== */
.pj { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1.05fr); gap: 56px; align-items: start; }
.pj-list { display: flex; flex-direction: column; }
.pj-i {
  display: grid; grid-template-columns: 24px 40px minmax(0,1fr); gap: 16px;
  padding: 20px 0; border-bottom: 1px solid var(--line);
  background: none; border-left: 0; border-right: 0; border-top: 0;
  text-align: left; cursor: pointer; color: inherit; width: 100%;
}
.pj-i:first-child { border-top: 1px solid var(--line); }
.pj-ic { grid-row: 1 / span 2; padding-top: 5px; }
.pj-ic svg { width: 20px; height: 20px; color: var(--mut-2); transition: color .22s; }
.pj-i.on .pj-ic svg, .pj-i:hover .pj-ic svg { color: var(--cy); }
.pj-n { font-family: var(--disp); font-size: .82rem; font-weight: 700; color: var(--mut-2);
  padding-top: 4px; grid-row: 1 / span 2; }
/* grid-row is explicit because a third child in a two column grid otherwise
   falls back into column one and renders about six characters wide */
.pj-t { font-family: var(--disp); font-size: 1.14rem; font-weight: 700; color: var(--mut);
  transition: color .22s; margin: 0; }
.pj-m { font-size: .85rem; color: var(--mut-2); margin: 3px 0 0; }
.pj-i.on .pj-t, .pj-i:hover .pj-t { color: var(--txt); }
.pj-i.on .pj-n { color: var(--cy); }
.pj-i:focus-visible { outline: 2px solid var(--cy-lt); outline-offset: 3px; }
/* Tim, 28 Aug: "make image be longer so it fills out the space". A 16:10 crop
   left roughly half the column empty beside a six row index. 4:5 carries the
   full height of the list without the frame having to be cropped so hard that
   the job in the photo stops being readable. */
.pj-fig {
  position: sticky; top: calc(var(--hd-h) + 26px); border-radius: var(--r);
  overflow: hidden; border: 1px solid var(--line); background: var(--ink-2);
  aspect-ratio: 4 / 5;
}
.pj-fig img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover;
  opacity: 0; transition: opacity .5s ease; }
.pj-fig img.on { opacity: 1; }

/* Tim, 28 Aug: "make this section light colour white background" — the one
   section that breaks from photography to plain paper, so the project list
   reads like a spec sheet on a desk rather than another dark frame. Scoped to
   #work only: every color below is a straight override, nothing structural,
   so .sec/.pj stay shared with the dark sections either side of it. */
#work { background: var(--paper); color: var(--ink); }
#work .eyebrow { color: var(--cy-dp); }
#work h2 { color: var(--ink); }
#work h2 em { color: var(--cy-dp); }
#work .lede { color: #4B5560; }
#work .pj-i { border-bottom-color: rgba(10,11,12,.10); }
#work .pj-i:first-child { border-top-color: rgba(10,11,12,.10); }
#work .pj-ic svg { color: #A7AFB6; }
#work .pj-i.on .pj-ic svg, #work .pj-i:hover .pj-ic svg { color: var(--cy-dp); }
#work .pj-n { color: #A7AFB6; }
#work .pj-i.on .pj-n { color: var(--cy-dp); }
#work .pj-t { color: #5B6570; }
#work .pj-i.on .pj-t, #work .pj-i:hover .pj-t { color: var(--ink); }
#work .pj-m { color: #838B93; }
#work .pj-i:focus-visible { outline-color: var(--cy-dp); }
#work .pj-fig { border-color: rgba(10,11,12,.10); background: #EEF1F3; }
#work .ph { color: #5B6570; background: rgba(10,11,12,.035); border-color: rgba(10,11,12,.10); }
#work .ph b { color: var(--ink); }

/* ===========================================================================
   SITE SECURITY POLES. Imran: "we don't advertise it at all, so I sort of just
   want something where people can actually go to a website to see products and
   prices". So this is the only place on the page with a price, and every
   number in it is labelled as an example rather than presented as his rate,
   because he has not given us a rate list.
   =========================================================================== */
.pole {
  position: relative; overflow: hidden; isolation: isolate;
  background: var(--ink-2);
  border-top: 1px solid var(--line); border-bottom: 1px solid var(--line);
}
/* Tim, 28 Aug: "have background image as well". The band was a flat graphite
   field between two photographed sections, which made it read as a spec sheet
   dropped into the page rather than part of it. Same construction as .ev-bg,
   and deliberately a DIFFERENT frame from the pole photo in the column so the
   section is not showing the same picture twice. */
.pole-bg { position: absolute; inset: 0; z-index: 0; }
.pole-bg img { width: 100%; height: 100%; object-fit: cover; }
.pole-bg::after {
  content: ''; position: absolute; inset: 0;
  background:
    linear-gradient(180deg, rgba(18,20,22,.94) 0%, rgba(18,20,22,.86) 40%, rgba(18,20,22,.96) 100%),
    linear-gradient(266deg, rgba(18,20,22,.90) 0%, rgba(18,20,22,.62) 52%, rgba(18,20,22,.88) 100%);
}
.pole > .wrap { position: relative; z-index: 2; }

/* align-items:stretch, not center. Tim, 28 Aug: "if image like this make sure
   its longer". The photo was a 16:9 crop floating in the middle of a column
   twice its height, so the section finished with a long empty gutter under it.
   Stretched, the frame is as tall as the price list beside it and the two
   columns land together. The img has to be absolutely positioned for that to
   work: a stretched grid item gives the figure a height, but an in-flow img
   would keep its own aspect ratio and leave the box half empty. */
.pole-grid { display: grid; grid-template-columns: minmax(0,.92fr) minmax(0,1fr); gap: 52px; align-items: stretch; }
.pole-fig {
  position: relative; border-radius: var(--r); overflow: hidden;
  border: 1px solid var(--line); min-height: 460px;
}
.pole-fig img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; }
.pole-rows { display: flex; flex-direction: column; margin-top: 26px; }
.pr {
  display: grid; grid-template-columns: minmax(0,1fr) auto; gap: 18px; align-items: baseline;
  padding: 17px 0; border-bottom: 1px solid var(--line);
}
.pr:first-child { border-top: 1px solid var(--line); }
/* display:block is load bearing on every one of these. They are spans nested
   inside a span (so the grid gets two children, not four), and a span is
   inline by default, so without this the name and its description run together
   on one line as a single paragraph. */
.pr-n { display: block; font-family: var(--disp); font-size: 1.02rem; font-weight: 700; color: var(--txt); }
.pr-d { display: block; font-size: .85rem; color: var(--mut); margin: 3px 0 0; }
.pr-p { font-family: var(--disp); font-size: 1.14rem; font-weight: 700; color: var(--cy-lt);
  white-space: nowrap; }
.pr-p small { display: block; font-family: var(--body); font-size: .7rem; font-weight: 500;
  color: var(--mut-2); letter-spacing: .04em; text-align: right; margin-top: 2px; }

/* ---------- the honest label. Used anywhere a number or a claim is ours
     rather than his, so nothing on this page can be read as a promise he has
     not made. ---------- */
/* A BLOCK, not an inline-flex. As a flex container the bold lead in and the
   sentence after it become two separate flex items, so the bold one collapses
   into a narrow column and the label reads as two ragged paragraphs. */
.ph {
  display: block; margin-top: 22px; max-width: 74ch;
  padding: 11px 15px; border-radius: 8px; font-size: .81rem; line-height: 1.5;
  color: var(--mut); background: rgba(255,255,255,.04); border: 1px solid var(--line);
}
.ph b { color: var(--txt); font-weight: 600; }

/* ---------- EV band ---------- */
.ev { position: relative; overflow: hidden; isolation: isolate; }
.ev-bg { position: absolute; inset: 0; z-index: 0; }
.ev-bg img { width: 100%; height: 100%; object-fit: cover; }
/* The scrim has to stay heavy where the copy is and get out of the way where
   it is not, otherwise the photograph may as well not be there. The first pass
   ran .96 to .44 and the garage read as flat black on every screen. */
.ev-bg::after {
  content: ''; position: absolute; inset: 0;
  background:
    linear-gradient(92deg, rgba(10,11,12,.97) 0%, rgba(10,11,12,.93) 38%, rgba(10,11,12,.52) 66%, rgba(10,11,12,.20) 100%),
    linear-gradient(180deg, rgba(10,11,12,.55) 0%, rgba(10,11,12,0) 30%, rgba(10,11,12,0) 70%, rgba(10,11,12,.55) 100%);
}
.ev > .wrap { position: relative; z-index: 2; }
.ev-in { max-width: 56ch; }
.ev-pts { display: flex; flex-direction: column; margin: 26px 0 30px; }
.ev-pt { display: grid; grid-template-columns: 22px minmax(0,1fr); gap: 14px; align-items: start;
  padding: 13px 0; border-bottom: 1px solid var(--line); font-size: .95rem; color: var(--mut); }
.ev-pt:first-child { border-top: 1px solid var(--line); }
.ev-pt svg { width: 17px; height: 17px; margin-top: 4px; stroke: var(--cy); fill: none;
  stroke-width: 2.1; stroke-linecap: round; stroke-linejoin: round; }
.ev-pt b { color: var(--txt); font-weight: 600; }

/* ---------- the EV charge loop ----------
   Tim, 28 Aug: "lets add in animation of car, plug in, charge, recycle etc".

   It sits INSIDE .ev-in, in the copy column, rather than out over the photo:
   the background frame has the electrician on the right and anything floated
   there covers the one part of the picture worth seeing.

   Every part runs off the same 11s duration and the same delay of zero, so
   the phases stay locked to each other with no script. Times below are the
   percentages of that one loop:
     0 to 16    the car pulls in
     16 to 26   the lead reaches out and plugs in
     26 to 74   the four cells fill, one every 12 per cent
     74 to 84   full, gauge holds
     84 to 100  lead retracts, car drives off, and round it goes
   -------------------------------------------------------------------------- */
.evx {
  margin: 4px 0 30px; padding: 18px 20px 14px;
  border: 1px solid var(--line); border-radius: var(--r);
  background: rgba(255,255,255,.03);
}
.evx-svg { width: 100%; height: auto; display: block; }

.evx-ground { stroke: rgba(255,255,255,.14); stroke-width: 2; stroke-linecap: round; }
.evx-line { stroke: var(--mut-3); stroke-width: 3; stroke-linecap: round; }
.evx-box { fill: rgba(255,255,255,.06); stroke: var(--line-2); stroke-width: 2; }
.evx-bolt { fill: var(--cy); animation: evxBolt 11s var(--ease) infinite; }

.evx-cable {
  stroke: var(--cy); stroke-width: 3; stroke-linecap: round; fill: none;
  /* one length for the dash and the offset, so the path is fully hidden at
     rest and fully drawn at zero. 200 comfortably clears the real path. */
  stroke-dasharray: 260; stroke-dashoffset: 260;
  animation: evxCable 11s var(--ease) infinite;
}

.evx-car { animation: evxDrive 11s var(--ease) infinite; }
.evx-body { fill: rgba(255,255,255,.10); stroke: var(--txt); stroke-width: 2.4;
  stroke-linejoin: round; }
.evx-glass { fill: rgba(0,173,239,.16); stroke: var(--mut); stroke-width: 2; }
.evx-pillar { stroke: var(--mut); stroke-width: 2; stroke-linecap: round; }
.evx-wheel circle { fill: var(--ink); stroke: var(--txt); stroke-width: 2.4; }
.evx-wheel .evx-hub { fill: none; stroke: var(--mut-3); stroke-width: 2.4; }
.evx-port { fill: var(--ink); stroke: var(--cy); stroke-width: 2.4;
  animation: evxPort 11s var(--ease) infinite; }

.evx-cell { fill: rgba(255,255,255,.07); stroke: var(--line-2); stroke-width: 1.5; }
/* nth-of-type, not nth-child: the group holds only rects today, but a label or
   an outline dropped in later would silently shift every delay by one. */
.evx-cell:nth-of-type(1) { animation: evxCell 11s linear infinite; }
.evx-cell:nth-of-type(2) { animation: evxCell 11s linear infinite; animation-delay: 1.32s; }
.evx-cell:nth-of-type(3) { animation: evxCell 11s linear infinite; animation-delay: 2.64s; }
.evx-cell:nth-of-type(4) { animation: evxCell 11s linear infinite; animation-delay: 3.96s; }

.evx-cap {
  position: relative; height: 22px; margin-top: 10px;
  font-size: .74rem; font-weight: 600; letter-spacing: .16em;
  text-transform: uppercase; color: var(--cy-lt);
}
.evx-cap span {
  position: absolute; left: 0; top: 0; opacity: 0;
  animation: evxCap 11s var(--ease) infinite;
}
.evx-cap span:nth-child(2) { animation-delay: 2.2s; }
.evx-cap span:nth-child(3) { animation-delay: 4.4s; }
.evx-cap span:nth-child(4) { animation-delay: 7.2s; }

@keyframes evxDrive {
  0%       { transform: translateX(-340px); }
  16%, 84% { transform: translateX(0); }
  100%     { transform: translateX(620px); }
}
@keyframes evxCable {
  0%, 16%   { stroke-dashoffset: 260; }
  26%, 84%  { stroke-dashoffset: 0; }
  92%, 100% { stroke-dashoffset: 260; }
}
@keyframes evxPort {
  0%, 20%  { fill: var(--ink); }
  26%, 84% { fill: var(--cy); }
  92%,100% { fill: var(--ink); }
}
@keyframes evxCell {
  0%, 23%   { fill: rgba(255,255,255,.07); }
  27%, 84%  { fill: var(--cy); }
  88%, 100% { fill: rgba(255,255,255,.07); }
}
@keyframes evxBolt {
  0%, 24%  { opacity: .35; }
  30%, 82% { opacity: 1; }
  90%,100% { opacity: .35; }
}
@keyframes evxCap {
  0%              { opacity: 0; transform: translateY(4px); }
  3%, 18%         { opacity: 1; transform: none; }
  21%, 100%       { opacity: 0; transform: translateY(-4px); }
}

/* ---------- the satisfied customer photo, under the charge loop ---------- */
.ev-cst {
  display: flex; align-items: center; gap: 16px; margin-bottom: 26px;
  padding: 12px; border-radius: var(--r);
  background: rgba(255,255,255,.03); border: 1px solid var(--line);
}
.ev-cst img {
  width: 108px; height: 108px; object-fit: cover; border-radius: calc(var(--r) - 3px);
  flex: none;
}
.ev-cst p { font-size: .84rem; color: var(--mut); margin: 0; }
.ev-cst p b { display: block; font-family: var(--disp); font-weight: 700;
  font-size: .98rem; color: var(--txt); margin-bottom: 3px; }

/* ---------- reviews ---------- */
.rv { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 20px; }
.rv-c {
  position: relative; padding: 30px 26px 26px; border-radius: var(--r);
  background: var(--ink-2); border: 1px solid var(--line);
}
.rv-c::before {
  content: '\201C'; position: absolute; bottom: 14px; right: 20px;
  font-family: var(--disp); font-size: 4.6rem; line-height: 1; color: rgba(255,255,255,.05);
  pointer-events: none;
}
/* bottom, not top. Concept 1 learned that the hard way: at the top the quote
   mark paints straight through the first line of every review. */
.rv-s { display: flex; gap: 3px; margin-bottom: 14px; }
.rv-s svg { width: 15px; height: 15px; fill: var(--cy); }
.rv-q { font-size: .98rem; color: var(--txt); margin: 0 0 16px; position: relative; z-index: 1; }
.rv-w { font-size: .84rem; color: var(--mut); font-weight: 600; }

/* ---------- the short form under the reviews ----------
   Tim, 28 Aug: "just add a simple form here". Everything below the two review
   cards was empty, which is the exact point in the page where someone has just
   read that other people rated them and has nowhere to act on it.

   Name and mobile ONLY, and it says out loud that a person rings back. Every
   extra field is another reason to abandon it, and the job gets scoped on the
   call rather than in a textarea. The disclosure line is not optional: this is
   a concept page and the form genuinely posts nowhere. */
/* Tim, 28 Aug: "put this under the reviews not next to it". The note and the
   form were a two column row, which read as a sidebar beside the reviews
   rather than the next thing to do after reading them. Stacked, the form is
   full width and unmistakably the step that follows. */
.rv-foot {
  display: grid; grid-template-columns: minmax(0,1fr);
  gap: 20px; align-items: start; margin-top: 20px;
}
.rv-foot .ph { margin-top: 0; }
.rvf {
  padding: 26px; border-radius: var(--r);
  background: var(--ink-2); border: 1px solid var(--line);
}
.rvf-t { font-family: var(--disp); font-size: 1.24rem; font-weight: 700;
  letter-spacing: -.018em; color: var(--txt); margin: 0; }
.rvf-s { font-size: .9rem; color: var(--mut); margin: 6px 0 18px; }
.rvf-fields { display: grid; grid-template-columns: repeat(2, minmax(0,1fr)); gap: 12px; }
.rvf-f { display: flex; flex-direction: column; gap: 6px; }
.rvf-f > span {
  font-size: .7rem; font-weight: 600; letter-spacing: .13em;
  text-transform: uppercase; color: var(--mut-2);
}
.rvf-f input {
  font-family: var(--body); font-size: .95rem; color: var(--txt);
  min-height: 50px; padding: 0 14px; border-radius: var(--r);
  background: rgba(255,255,255,.04); border: 1px solid var(--line-2);
  transition: border-color .2s, background .2s;
}
.rvf-f input::placeholder { color: var(--mut-3); }
.rvf-f input:hover { background: rgba(255,255,255,.06); }
.rvf-f input:focus { outline: none; border-color: var(--cy); background: rgba(255,255,255,.07); }
/* the accent lands on a VALID field, not on every field that has been touched.
   :valid alone would light an empty optional input, so both are required and
   :placeholder-shown keeps it dark until something is actually typed. */
.rvf-f input:valid:not(:placeholder-shown) { border-color: rgba(0,173,239,.5); }
.rvf-go { width: 100%; margin-top: 16px; }
.rvf-note { font-size: .78rem; color: var(--mut-2); margin: 14px 0 0; }
.rvf.done .rvf-fields, .rvf.done .rvf-go { display: none; }
.rvf.done .rvf-note { color: var(--cy-lt); font-weight: 600; }

/* ---------- contact ---------- */
.ct { display: grid; grid-template-columns: minmax(0,1fr) minmax(0,1fr); gap: 52px; align-items: start; }
.ct-l { display: flex; flex-direction: column; }
.ct-r {
  display: grid; grid-template-columns: 20px minmax(0,1fr); gap: 16px; align-items: start;
  padding: 18px 0; border-bottom: 1px solid var(--line); text-decoration: none;
}
.ct-r:first-of-type { border-top: 1px solid var(--line); }
.ct-r svg { width: 17px; height: 17px; margin-top: 4px; stroke: var(--cy); fill: none;
  stroke-width: 2; stroke-linecap: round; stroke-linejoin: round; }
/* same nested span trap as the pole rows: block, or the label and the value
   run together on one line */
.ct-k { display: block; font-size: .74rem; letter-spacing: .12em; text-transform: uppercase; color: var(--mut-2); }
.ct-v { display: block; font-family: var(--disp); font-size: 1.14rem; font-weight: 700; color: var(--txt); margin-top: 2px; }

/* ---------- footer ---------- */
.ft { border-top: 1px solid var(--line); padding: 56px 0 34px; background: var(--ink-2); }
.ft-top { display: flex; flex-wrap: wrap; gap: 36px; justify-content: space-between; align-items: flex-start; }
.ft-brand img { height: 40px; width: auto; }
.ft-brand p { margin-top: 16px; font-size: .88rem; color: var(--mut); max-width: 40ch; }
.ft-cols { display: flex; flex-wrap: wrap; gap: 52px; }
.ft-col h4 { font-family: var(--body); font-size: .74rem; font-weight: 600; letter-spacing: .14em;
  text-transform: uppercase; color: var(--mut-2); margin-bottom: 6px; }
.ft-col a { display: flex; align-items: center; min-height: 40px; font-size: .9rem;
  color: var(--mut); text-decoration: none; transition: color .2s; }
.ft-col a:hover { color: var(--txt); }
.ft-bot {
  margin-top: 44px; padding-top: 22px; border-top: 1px solid var(--line);
  display: flex; flex-wrap: wrap; gap: 12px 24px; justify-content: space-between;
  align-items: center; font-size: .82rem; color: var(--mut-2);
}
/* the attribution. Themed in this site's own footer font and muted colour with
   the brand word in this site's accent, never a fixed Scalus coloured chip. */
.by { display: inline-block; color: var(--mut-2); text-decoration: none;
  min-height: 40px; line-height: 40px; }
.by b { color: var(--cy); font-weight: 600; }
.by:hover b { color: var(--cy-lt); }
.ft-bot a { color: inherit; }

/* ===========================================================================
   LIQUID GLASS DOCK — the Netflix iOS 26 tab bar, ported from scalus.app.

   Four layers make it read as real glass rather than a translucent box:
   a blur plus saturate plus BRIGHTNESS LIFT (the lift is the single biggest
   tell), a masked rotating rim, a specular hotspot that tracks the pointer,
   and a pill that slides behind the active item with its own inner highlight
   so it sits inside the glass rather than on top of it.

   TRAP, and the reason the wrapper is a flex row rather than the usual
   left:50% plus translateX(-50%): a TRANSFORMED ANCESTOR breaks
   backdrop-filter on its child in Chrome and Safari. The filter still computes,
   so devtools and getComputedStyle both look correct, and the glass silently
   renders flat. Every transform lives on .dk-in or below it.
   =========================================================================== */
@property --r3rim { syntax: '<angle>'; inherits: false; initial-value: 200deg; }

.dk {
  position: fixed; z-index: 75; left: 0; right: 0;
  bottom: calc(18px + env(safe-area-inset-bottom));
  display: flex; justify-content: center; padding: 0 12px;
  opacity: 1; transition: opacity .3s ease; pointer-events: none;
}
.dk.hide { opacity: 0; }
.dk.hide .dk-in { transform: translateY(18px) scale(.96); }

.dk-in {
  pointer-events: auto; position: relative; isolation: isolate; max-width: 100%;
  display: flex; align-items: stretch; gap: 1px; padding: 7px; border-radius: 27px;
  background: linear-gradient(180deg, rgba(30,34,38,.72), rgba(7,8,9,.86));
  -webkit-backdrop-filter: blur(26px) saturate(200%) brightness(1.28);
  backdrop-filter: blur(26px) saturate(200%) brightness(1.28);
  box-shadow: 0 26px 54px -14px rgba(0,0,0,.78), 0 3px 12px rgba(0,0,0,.4),
    inset 0 1px 0 rgba(255,255,255,.16), inset 0 -1px 0 rgba(255,255,255,.05);
  transform: translateY(0) scale(1);
  transition: transform .38s cubic-bezier(.2,.8,.2,1);
}
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .dk-in { background: rgba(14,16,18,.97); }
}

/* the rim: a conic gradient painted only on a 1px ring, rotating slowly so the
   highlight travels around the edge the way a moulded glass edge catches light */
.dk-in::before {
  content: ''; position: absolute; inset: 0; border-radius: inherit; padding: 1px;
  z-index: 2; pointer-events: none;
  background: conic-gradient(from var(--r3rim),
    rgba(255,255,255,.6), rgba(255,255,255,.05) 18%,
    rgba(0,173,239,.44) 38%, rgba(255,255,255,.04) 55%,
    rgba(255,255,255,.5) 76%, rgba(255,255,255,.08) 90%, rgba(255,255,255,.6));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
  animation: r3rim 16s linear infinite;
}
@keyframes r3rim { to { --r3rim: 560deg; } }

/* the refractive edge. Two mask composited RINGS, so a backdrop-filter can be
   applied to an edge and nothing else. A radial fade would be simpler but it
   is an ellipse, and on a box this wide and short it reaches the vertical
   centre almost immediately and fogs the whole dock. */
.dk-lens {
  position: absolute; inset: 0; border-radius: inherit; padding: 11px; z-index: 1;
  pointer-events: none; opacity: .6;
  -webkit-backdrop-filter: blur(2px) saturate(1.8) brightness(1.14);
  backdrop-filter: blur(2px) saturate(1.8) brightness(1.14);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}
.dk-edge {
  position: absolute; inset: 0; border-radius: inherit; padding: 3.5px; z-index: 2;
  pointer-events: none;
  -webkit-backdrop-filter: blur(6px) saturate(3.2) brightness(1.38) contrast(1.12);
  backdrop-filter: blur(6px) saturate(3.2) brightness(1.38) contrast(1.12);
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor; mask-composite: exclude;
}

/* the specular hotspot, position set from --dx/--dy in r3.js */
.dk-in::after {
  content: ''; position: absolute; inset: 0; border-radius: inherit; z-index: 1;
  pointer-events: none;
  background: radial-gradient(130px 66px at var(--dx,28%) var(--dy,-10%),
    rgba(255,255,255,.2), rgba(255,255,255,.05) 45%, rgba(255,255,255,0) 72%);
}

.dk-pill {
  position: absolute; top: 7px; bottom: 7px; left: 0; z-index: 0; width: var(--w,0px);
  border-radius: 21px; pointer-events: none; opacity: 0;
  transform: translate3d(var(--x,0px),0,0);
  background: linear-gradient(180deg, rgba(255,255,255,.16), rgba(255,255,255,.055));
  box-shadow: inset 0 1px 0 rgba(255,255,255,.3), inset 0 -1px 0 rgba(0,0,0,.22),
    0 4px 14px rgba(0,0,0,.3);
  transition: transform .5s cubic-bezier(.34,1.35,.5,1), width .5s cubic-bezier(.34,1.35,.5,1), opacity .35s;
}
.dk.ready .dk-pill { opacity: 1; }

.dk-i {
  position: relative; z-index: 3; display: flex; flex-direction: column; align-items: center;
  justify-content: center; gap: 4px; min-width: 64px; padding: 8px 8px 7px; border-radius: 21px;
  text-decoration: none; color: var(--mut); -webkit-tap-highlight-color: transparent;
  background: none; border: 0; cursor: pointer;
  transition: color .25s ease, transform .2s ease;
}
.dk-i:hover { color: var(--txt); }
.dk-i:active { transform: scale(.93); }
.dk-i.on { color: #fff; }
.dk-i .ic { width: 26px; height: 26px; display: grid; place-items: center; flex: none; }
.dk-i .ic svg { width: 22px; height: 22px; display: block; stroke: currentColor; fill: none;
  stroke-width: 1.8; stroke-linecap: round; stroke-linejoin: round; }
.dk-i .lb { font-size: .64rem; font-weight: 650; line-height: 1; white-space: nowrap;
  font-family: var(--body); }
.dk-i:focus-visible { outline: 2px solid var(--cy-lt); outline-offset: 2px; }

/* exactly ONE filled tile in the row, so the dock has a single focal point.
   Call is an action rather than a page, which is why it gets it. */
.dk-i.cta { color: var(--txt); }
.dk-i.cta .ic {
  border-radius: 9px; color: var(--on-cy);
  background: linear-gradient(160deg, #4CCBFF, #0090CC);
  box-shadow: 0 0 16px var(--cy-glow), inset 0 1px 0 rgba(255,255,255,.45);
}
.dk-i.cta .ic svg { width: 15px; height: 15px; stroke: currentColor; stroke-width: 2.1; }

/* ===========================================================================
   HERO QUIZ, scoped overrides.
   The shared bundle sets its quietest tier to a percentage of white that
   composites to about 4.04:1 on this panel, so the step counter and the
   privacy line both fail. Fixed HERE rather than in the bundle, because
   raising a shared default to suit one client changes every concept ever
   built. Emitted after the bundle's own stylesheet so it wins on source order.

   .hq-next is enumerated with :not(:disabled) and :disabled listed separately:
   .hq-next:disabled is more specific than a plain .hq-next rule, so a single
   override either loses to it or swallows it, and the disabled state is the
   one a screenshot pass never catches because you photograph the panel before
   anyone has interacted with it.
   =========================================================================== */
.hq .hq-step { color: #A8B0B8; }
.hq .hq-priv { color: #A8B0B8; }
.hq .hq-next:not(:disabled) { color: var(--on-cy); }
.hq .hq-next:disabled { color: #C3CBD3; }

/* ---------------------------------------------------------------------------
   The card's own lockup, swapped in by r3.js. See the note there for why.

   The 22px left padding is not a guess and is not decoration: .hq-live is
   padded 20px 22px 22px, so 22px is the left edge the progress bar, the
   question, every option row and the privacy line all sit on. Anything else
   here and the header is aligned to nothing.

   Specificity, not source order, is what makes these win. r3.css is linked
   BEFORE the bundle's stylesheet, so .hq-brand.r3-lk on its own would lose to
   the bundle's plain .hq-brand. The .hq prefix takes it to three classes.
   --------------------------------------------------------------------------- */
/* Tim, 28 Aug: "centre the logo on the form". It was left aligned to share an
   edge with the question and the options below it, but the mark reads as the
   card's masthead rather than another row of content, so it centres over them
   instead. The rule below it still spans the full card, which is what keeps
   the header and the body reading as one panel. */
.hq .hq-brand.r3-lk {
  display: block; text-align: center;
  padding: 18px 22px 14px;
}
.r3-lk-logo { margin-left: auto; margin-right: auto; }
.hq .hq-brand.r3-lk > span { display: block; }
/* the actual logo file, used only as a mask so an animated blue gradient
   shines through its exact shape — the real artwork, not a redraw of it.
   Bigger than the 34px logo in the header on purpose: this card is the thing
   a visitor is actually looking at, the header logo is a wayfinding mark. */
.r3-lk-logo {
  width: 172px; aspect-ratio: 901 / 276;
  -webkit-mask-image: url(/right-1-group-media/logo-horizontal-light.png);
  -webkit-mask-size: 100% 100%; -webkit-mask-repeat: no-repeat; -webkit-mask-position: left center;
  mask-image: url(/right-1-group-media/logo-horizontal-light.png);
  mask-size: 100% 100%; mask-repeat: no-repeat; mask-position: left center;
  background-image: linear-gradient(100deg,
    var(--cy-lt) 0%, var(--cy) 20%, #EAF9FF 36%, var(--cy) 52%, var(--cy-dp) 72%, var(--cy-lt) 100%);
  background-size: 260% 100%;
  /* two animations on one element: the endless shine, plus a one shot entrance.
     Tim, 28 Aug: "animate this in when page loads". The lockup is injected by
     r3.js the moment the bundle mounts the card, so the entrance fires on
     insert, which IS page load from a visitor's point of view, and it needs no
     load event to hang off. */
  animation: r3LogoShine 5s linear infinite, r3LkIn .8s var(--ease) both;
  filter: drop-shadow(0 0 9px var(--cy-glow));
}
@keyframes r3LogoShine { to { background-position: -260% 0; } }
/* the mark lands first, then the two lines under it follow it in, so the
   header reads top down the way it would be spoken rather than all at once */
@keyframes r3LkIn {
  from { opacity: 0; transform: translateY(-9px) scale(.96); }
  to   { opacity: 1; transform: none; }
}
@keyframes r3LkLine {
  from { opacity: 0; transform: translateY(7px); }
  to   { opacity: 1; transform: none; }
}
.r3-lk-2 {
  font-family: var(--body); font-weight: 600;
  font-size: .74rem; line-height: 1.3; letter-spacing: .19em;
  text-transform: uppercase; color: var(--hq-fg); margin-top: 6px;
  animation: r3LkLine .6s var(--ease) .26s both;
}
.r3-lk-3 {
  font-family: var(--body); font-weight: 500;
  font-size: .66rem; line-height: 1.3; letter-spacing: .13em;
  text-transform: uppercase; color: #A8B0B8; margin-top: 2px;
  animation: r3LkLine .6s var(--ease) .38s both;
}

/* ---------- pinned mobile enquiry bar ---------- */
/* 96px of clearance, not 84. The dock sits 18px off the bottom and is about
   66px tall, so its top edge lands at 84px and an 84px offset here put the two
   in contact by 3px. A pinned bar touching the dock reads as one broken
   element and the top row of the dock stops being tappable. */
.pin {
  position: fixed; left: 12px; right: 12px; z-index: 74;
  bottom: calc(96px + env(safe-area-inset-bottom));
  display: none; gap: 10px;
  opacity: 0; transform: translateY(16px); pointer-events: none;
  transition: opacity .35s, transform .35s var(--ease);
}
.pin.on { opacity: 1; transform: none; pointer-events: auto; }
.pin .btn { flex: 1 1 0; min-width: 0; }

/* ---------- reveals ---------- */
.reveal { opacity: 0; transform: translateY(20px); filter: blur(7px);
  transition: opacity .8s var(--ease), transform .8s var(--ease), filter .8s var(--ease); }
.reveal.in, .noanim .reveal { opacity: 1; transform: none; filter: none; }
.stag > * { opacity: 0; transform: translateY(16px);
  transition: opacity .7s var(--ease), transform .7s var(--ease); }
.stag.in > *, .noanim .stag > * { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .reveal, .stag > * { opacity: 1 !important; transform: none !important; filter: none !important; }
  .slide.on img { animation: none; transform: scale(1.06); }
  /* the charge loop settles on its plugged in, fully charged frame rather
     than disappearing, so the panel still says what it is there to say */
  .evx-car, .evx-cable, .evx-port, .evx-cell, .evx-bolt, .evx-cap span { animation: none; }
  .evx-cable { stroke-dashoffset: 0; }
  .evx-port { fill: var(--cy); }
  .evx-cell { fill: var(--cy); }
  .evx-cap span { opacity: 0; }
  .evx-cap span:last-child { opacity: 1; }
  .r3-lk-logo, .hh em { animation: none; background-position: 0 0; }
  .r3-lk-2, .r3-lk-3 { animation: none; }
  .dk-in::before { animation: none; }
  .rail-b.on .fill { transition: none; width: calc(100% - 2px); }
  * { scroll-behavior: auto !important; }
}

/* ===========================================================================
   RESPONSIVE. Mobile is the priority surface, not an afterthought — most of
   the traffic to this page will arrive from a phone.
   =========================================================================== */
@media (max-width: 1080px) {
  .hero-grid { grid-template-columns: 1fr; gap: 34px; align-items: start; }
  /* the quiz only takes the right hand visual's slot while that visual is
     actually on the right, which stops being true the moment the hero stacks.
     Below this width the bundle takes its fallback path and the placeholder is
     left behind as 470px of dead space above the panel unless it is hidden. */
  .hero-slot { display: none; }
  .pj { grid-template-columns: 1fr; gap: 32px; }
  /* back to the wide crop once stacked: the 4:5 frame exists to match the
     height of a list sitting BESIDE it, and stacked there is no list beside
     it, so it would just push the projects half a screen down the page */
  .pj-fig { position: static; order: -1; aspect-ratio: 16 / 10; }
  .pole-grid { grid-template-columns: 1fr; gap: 34px; }
  /* stacked, there is no second column to match, so the frame goes back to a
     crop of its own. min-height stays out of the way or it makes a phone
     scroll past half a screen of photo before it reaches the prices. */
  .pole-fig { min-height: 0; aspect-ratio: 16 / 10; }
  .ct { grid-template-columns: 1fr; gap: 36px; }
}

@media (max-width: 900px) {
  .nav { display: none; }
  .hd-cta { display: none; }
  .burger { display: flex; }
  .brand img { height: 30px; }
  .rv { grid-template-columns: 1fr; }
  .rv-foot { grid-template-columns: 1fr; }
}

@media (max-width: 640px) {
  body { font-size: 16px; }
  :root { --hd-h: 66px; }
  :root { --pad: 18px; }
  .hero { min-height: 92svh; padding-top: calc(var(--hd-h) + 26px); }
  .hero-copy { max-width: none; }
  /* the hero's twin buttons are dropped here: the glass quiz panel is the
     stronger call to action and the buttons were pushing it about 140px down
     the screen. The pinned bar below carries both actions instead. */
  .hero-btns { display: none; }
  .pin { display: flex; }
  .cap { margin-top: 22px; min-height: 54px; }

  /* a flex or grid basis becomes a HEIGHT the moment a row flips to a column,
     so anything that was sized for a row is released explicitly here */
  .dv { grid-template-columns: 34px minmax(0,1fr); gap: 14px; padding: 22px 0; }
  .dv-go { grid-column: 2; margin-top: 8px; }
  .dv.lead { padding: 26px 0; }
  .pj-i { grid-template-columns: 18px 32px minmax(0,1fr); gap: 12px; }
  .pr { grid-template-columns: minmax(0,1fr) auto; gap: 12px; }
  .rvf { padding: 22px 18px; }
  .rvf-fields { grid-template-columns: 1fr; }
  .pr-p { font-size: 1rem; }
  .ft-top { gap: 30px; }
  .ft-cols { gap: 34px; }
  .sec { padding: 66px 0; }

  /* the dock squares up with the screen edges rather than floating as a pill,
     which is what the reference does at this width */
  .dk { padding: 0 12px; }
  .dk-in { width: 100%; }
  .dk-i { flex: 1 1 0; min-width: 0; }
  .dk-i .lb { font-size: .6rem; }

}

@media (max-width: 380px) {
  .dk-i { min-width: 0; padding: 8px 4px 7px; }
  .dk-i .lb { font-size: .56rem; }
}

/* ===========================================================================
   THE CHAT. Tim, 28 Aug: "add a generic chat on the site as well".

   Scripted and disclaimed, same as the hero quiz and the review form. See
   r3-chat.js for why the conversation hands over to the phone after two
   answers rather than trying to quote.

   POSITIONING IS THE WHOLE PROBLEM HERE. Three fixed elements already live at
   the bottom of this page: the dock (bottom 18px, centred, about 66px tall)
   and on a phone the pinned enquiry bar (bottom 96px, about 52px tall). A
   bottom right bubble at the usual 20px would sit underneath both. So the
   button clears the dock on desktop, where the dock is centre width, and
   clears the pinned bar as well on a phone, where the dock spans full width.
   =========================================================================== */
.r3c-fab {
  position: fixed; z-index: 78; right: 22px;
  bottom: calc(22px + env(safe-area-inset-bottom));
  display: flex; align-items: center; gap: 9px;
  padding: 12px 18px 12px 15px; border: 0; border-radius: 999px;
  background: var(--cy); color: var(--on-cy); cursor: pointer;
  font-family: var(--body); font-size: .92rem; font-weight: 600;
  box-shadow: 0 10px 30px rgba(0,0,0,.45), 0 0 0 1px rgba(255,255,255,.10) inset;
  transition: transform .25s var(--ease), box-shadow .25s;
}
.r3c-fab svg { width: 19px; height: 19px; }
.r3c-fab:hover { transform: translateY(-2px); box-shadow: 0 14px 36px rgba(0,0,0,.5); }
.r3c-fab:focus-visible { outline: 2px solid var(--cy-lt); outline-offset: 3px; }
/* the label is redundant once the panel is open and the icon alone keeps the
   button out of the way of the panel it opened */
.r3c-fab.on span { display: none; }
.r3c-fab.on { padding: 13px; }

.r3c {
  position: fixed; z-index: 79; right: 22px;
  bottom: calc(84px + env(safe-area-inset-bottom));
  width: min(360px, calc(100vw - 44px)); max-height: min(560px, calc(100vh - 160px));
  display: flex; flex-direction: column; overflow: hidden;
  border-radius: 16px; border: 1px solid var(--line-2);
  background: var(--ink-2); color: var(--txt);
  box-shadow: 0 24px 60px rgba(0,0,0,.6);
  animation: r3cIn .3s var(--ease) both;
}
@keyframes r3cIn { from { opacity: 0; transform: translateY(14px) scale(.98); } }
/* An author display:flex outranks the user agent's [hidden]{display:none}, so
   without this the panel ships to the page already open on first paint. */
.r3c[hidden] { display: none; }

.r3c-hd {
  display: flex; align-items: center; gap: 12px;
  padding: 14px 14px 13px 18px; border-bottom: 1px solid var(--line);
  background: rgba(255,255,255,.03);
}
.r3c-hd-b { display: flex; flex-direction: column; flex: 1 1 auto; min-width: 0; }
.r3c-hd-b b { font-family: var(--disp); font-size: 1rem; font-weight: 700; color: var(--txt); }
.r3c-hd-b span { font-size: .75rem; color: var(--mut-2); }
.r3c-x {
  flex: none; width: 30px; height: 30px; padding: 0; display: grid; place-items: center;
  background: none; border: 0; border-radius: 8px; color: var(--mut); cursor: pointer;
  transition: background .2s, color .2s;
}
.r3c-x svg { width: 16px; height: 16px; }
.r3c-x:hover { background: rgba(255,255,255,.07); color: var(--txt); }
.r3c-x:focus-visible { outline: 2px solid var(--cy-lt); outline-offset: 2px; }

.r3c-log {
  flex: 1 1 auto; overflow-y: auto; padding: 16px 18px;
  display: flex; flex-direction: column; gap: 10px;
}
.r3c-m {
  max-width: 86%; padding: 10px 13px; border-radius: 13px;
  font-size: .89rem; line-height: 1.5; animation: r3cIn .25s var(--ease) both;
}
.r3c-bot { align-self: flex-start; background: rgba(255,255,255,.06);
  border: 1px solid var(--line); border-bottom-left-radius: 5px; }
.r3c-you { align-self: flex-end; background: var(--cy); color: var(--on-cy);
  font-weight: 500; border-bottom-right-radius: 5px; }
.r3c-call {
  display: inline-block; margin-top: 9px; padding: 8px 14px; border-radius: 999px;
  background: var(--cy); color: var(--on-cy); font-weight: 600; text-decoration: none;
}
.r3c-call:hover { background: var(--cy-lt); }

.r3c-dots { display: inline-flex; gap: 4px; padding: 3px 0; }
.r3c-dots span {
  width: 6px; height: 6px; border-radius: 50%; background: var(--mut);
  animation: r3cDot 1.2s infinite;
}
.r3c-dots span:nth-child(2) { animation-delay: .18s; }
.r3c-dots span:nth-child(3) { animation-delay: .36s; }
@keyframes r3cDot { 0%,60%,100% { opacity: .3; } 30% { opacity: 1; } }

.r3c-chips { display: flex; flex-wrap: wrap; gap: 7px; padding: 0 18px 4px; }
.r3c-chips:empty { display: none; }
.r3c-chip {
  padding: 7px 12px; border-radius: 999px; cursor: pointer;
  background: none; border: 1px solid var(--line-2); color: var(--mut);
  font-family: var(--body); font-size: .8rem; font-weight: 500;
  transition: border-color .2s, color .2s, background .2s;
}
.r3c-chip:hover { border-color: var(--cy); color: var(--cy-lt); background: rgba(0,173,239,.08); }
.r3c-chip:focus-visible { outline: 2px solid var(--cy-lt); outline-offset: 2px; }

.r3c-f { display: flex; gap: 8px; padding: 12px 14px 6px; }
.r3c-f input {
  flex: 1 1 auto; min-width: 0; padding: 11px 14px; border-radius: 999px;
  background: rgba(255,255,255,.05); border: 1px solid var(--line-2);
  color: var(--txt); font-family: var(--body); font-size: .88rem;
}
.r3c-f input::placeholder { color: var(--mut-3); }
.r3c-f input:focus { outline: none; border-color: var(--cy); background: rgba(255,255,255,.07); }
.r3c-f button {
  flex: none; width: 40px; height: 40px; display: grid; place-items: center;
  border: 0; border-radius: 50%; background: var(--cy); color: var(--on-cy); cursor: pointer;
}
.r3c-f button svg { width: 17px; height: 17px; }
.r3c-f button:hover { background: var(--cy-lt); }
.r3c-f button:focus-visible { outline: 2px solid var(--cy-lt); outline-offset: 2px; }
.r3c-note { font-size: .72rem; color: var(--mut-2); margin: 0; padding: 0 18px 14px; }

@media (max-width: 640px) {
  /* Two fixed bars live down there on a phone, but NOT at the same time. The
     dock is always present, so 96px is the resting offset that clears it. The
     pinned enquiry bar only appears once the visitor scrolls, so parking the
     bubble permanently above where it WOULD be left it floating in the middle
     of the hero card on first paint. :has watches for the bar actually being
     on and lifts the bubble only then. */
  .r3c-fab { right: 14px; bottom: calc(96px + env(safe-area-inset-bottom));
    transition: bottom .35s var(--ease); }
  body:has(.pin.on) .r3c-fab { bottom: calc(158px + env(safe-area-inset-bottom)); }
  .r3c { right: 12px; left: 12px; width: auto;
    bottom: calc(154px + env(safe-area-inset-bottom));
    max-height: min(460px, calc(100vh - 240px)); }
  body:has(.pin.on) .r3c { bottom: calc(216px + env(safe-area-inset-bottom)); }
}

@media (prefers-reduced-motion: reduce) {
  .r3c, .r3c-m { animation: none; }
  .r3c-dots span { animation: none; opacity: .6; }
}
