/* Contact dialog — design node 162:6812, with the active field state from
   163:7031.

   Launched from the nav's "Let's talk", which it grows out of. The morph
   itself is in js/contact.js; everything here is the resting design plus the
   states CSS can own on its own (float labels, hover, validity).

   Colour note: Figma paints the panel `#0b0` under `mix-blend-mode: overlay`,
   the same way it exports the nav's CTA — and, as there, its own render shows
   a flat green. It is a plain fill. */

.rdk-contact {
  position: fixed;
  inset: 0;
  z-index: 200;                       /* over the nav's 100 */
  /* Node 168:7189. Declared here because the dialog is the only thing with an
     error state so far — it belongs in tokens.css the moment anything else
     needs it. */
  --rdk-error: #ef0000;
}

.rdk-contact[hidden] {
  display: none;
}

/* The 80% is baked into the COLOUR, not carried on `opacity` — js/contact.js
   fades this element from 0 to 1 on open, so an opacity of 0.8 here would be
   overwritten and the scrim would land fully opaque. That is exactly what it
   did: solid #001407 with the page invisible behind it. Node 163:7008 is
   #001407 at 80%, and the page has to stay readable through it. */
.rdk-contact__scrim {
  position: absolute;
  inset: 0;
  background: rgba(0, 20, 7, 0.8);
}

/* Anchored top-right because that is where it comes FROM — the CTA sits in
   the same corner, so the growth reads as the button opening out rather than
   as a panel flying across the screen. */
.rdk-contact__panel {
  position: absolute;
  top: calc(24 * var(--u));
  right: calc(32 * var(--u));
  width: calc(879 * var(--u));
  max-width: calc(100vw - 32px);
  /* Never taller than the window: the form is long, and on a laptop the
     submit button would otherwise sit below the fold with no way to reach
     it. The inner block scrolls instead. */
  max-height: calc(100svh - 48 * var(--u));
  background: var(--rdk-green);
  border-radius: calc(26 * var(--u));
  /* Clips the bubble, and — during the morph — clips the full-size content
     back to the button's footprint, which is what makes it look like the
     panel is unfolding out of it. */
  overflow: clip;
  /* transform is GSAP's; the origin is the corner the button shares. */
  transform-origin: 100% 0;
  will-change: transform;
}

/* Figma's RENDER of node 162:6946, not the image behind it.

   Two things are baked into this file and must NOT be re-applied here:
   the 170.43deg rotation and vertical mirror, and — the reason it had to be
   re-exported — the image ADJUSTMENTS on the fill. The uploaded source is a
   pale yellow-green (140,194,33); as the design actually renders it, it is a
   deep saturated green (30,117,1). Nothing in the raw file carries that.

   It is also already cropped to the part the panel shows. The node is a
   524.299 box at top -203.67, so only its bottom 320.63 is ever visible, and
   that is exactly what the export contains — hence `top: 0` rather than the
   node's own negative offset. */
.rdk-contact__bubble {
  position: absolute;
  left: calc(346.83 * var(--u));
  top: 0;
  width: calc(524.299 * var(--u));
  height: calc(320.63 * var(--u));
  max-width: none;
  pointer-events: none;
}

.rdk-contact__close {
  position: absolute;
  top: calc(24 * var(--u));
  right: calc(24 * var(--u));
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(52 * var(--u));
  height: calc(52 * var(--u));
  padding: 0;                         /* UA button padding distorts the icon */
  color: var(--rdk-dark);
  background: var(--rdk-lime);
  border: 0;
  border-radius: calc(16 * var(--u));
  cursor: pointer;
  transition: scale 0.2s ease;
}

.rdk-contact__close svg {
  width: calc(16 * var(--u));
  height: calc(16 * var(--u));
}

.rdk-contact__close:hover,
.rdk-contact__close:focus-visible {
  scale: 1.08;
}

.rdk-contact__inner {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: calc(28 * var(--u));
  max-height: inherit;
  /* Top was 96 (client, 2026-08-28: reduce it). The sides and bottom are
     the design's. 64 still clears the close button, which is 52 tall at
     top 24 — they do not collide anyway, being at opposite ends of the row. */
  padding: calc(64 * var(--u)) calc(64 * var(--u)) calc(72 * var(--u));
  overflow-y: auto;
  /* Counter-scaled against the panel during the morph — see js/contact.js.
     Its own transform-origin has to match the panel's or the cancellation is
     not exact. */
  transform-origin: 100% 0;
}

.rdk-contact__head,
.rdk-contact__form {
  position: relative;                 /* above the bubble */
  width: 100%;
}

.rdk-contact__head {
  display: flex;
  flex-direction: column;
  gap: calc(16 * var(--u));
  width: calc(520 * var(--u));
  margin-right: auto;
  margin-bottom: 0;
}

  /* Display/L, from the file's own variables: 88 / lh 1 / tracking -4%.
     Tracking in that file is a PERCENTAGE, so -4 on 88 is -3.52 -- it was read
     as literal px here and shipped at -5.28, i.e. -6%, 50% tighter than the
     token and tighter than Display/XL above it. Corrected 2026-09-09; do not
     put -5.28 back. */
.rdk-contact__title {
  margin: 0;
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(88 * var(--du));
  line-height: 1;
  letter-spacing: calc(-3.52 * var(--du));
  color: var(--rdk-white);
}

.rdk-contact__blurb {
  margin: 0;
  font-family: var(--rdk-font-body);
  font-weight: var(--rdk-body-weight);
  font-size: var(--rdk-body-m);
  line-height: var(--rdk-body-lh);
  letter-spacing: var(--rdk-body-track);
  color: var(--rdk-white);
  opacity: 0.9;
  /* BALANCE, NOT THE BODY'S INHERITED `pretty` (client, 2026-09-17). `pretty`
     only pulls a word down to rescue a single short LAST line, so it does
     nothing for the shape this paragraph actually gets into: two sentences of
     very different length wrapping in a narrow panel. Balance evens them.
     The same call the intro headline already makes, for the same reason.

     It is a NO-OP on the desktop, where the designed break puts each sentence
     on its own line and there is nothing to even out, and it degrades safely:
     unsupported, or past the browser's line cap, it falls back to the ordinary
     wrapping that shipped before. */
  text-wrap: balance;
}

.rdk-contact__form {
  display: flex;
  flex-direction: column;
  gap: calc(20 * var(--u));
}

.rdk-contact__fields {
  display: flex;
  flex-direction: column;
  gap: calc(12 * var(--u));
}

.rdk-contact__row {
  display: flex;
  gap: calc(12 * var(--u));
}

.rdk-contact__row > .rdk-field {
  flex: 1 1 0;
  min-width: 0;
}

/* ---------- fields ----------
   Node 162:6949 (resting) and 163:7031 (active/filled), and 168:7189 for the
   error. The white box is `__box`; the message is its SIBLING, in flow, so it
   pushes what follows instead of painting over it — which is the 8u gap the
   design has between the two.

   A float label. At rest the label is the whole field, 18px and centred.
   Focused or filled it drops to 14 and lifts, with the value beneath it. Both
   states are directly transitionable properties, so `:has()` reading the input
   is the entire mechanism — no class, no JS. */

.rdk-field {
  display: flex;
  flex-direction: column;
  gap: calc(8 * var(--u));
}

.rdk-contact__row > .rdk-field {
  flex: 1 1 0;
  min-width: 0;
}

.rdk-field__box {
  position: relative;
  height: calc(72 * var(--u));
  background: var(--rdk-white);
  border-radius: calc(16 * var(--u));
  /* The design draws a 2px border in the error state. Drawn as an inset
     outline rather than a border so turning it on costs no layout — a real
     border would shrink the content box by 4u and shift the text. */
  outline: calc(2 * var(--u)) solid transparent;
  outline-offset: calc(-2 * var(--u));
  transition: outline-color 0.2s ease;
}

/* 274 in the design; 160 on client direction (2026-08-28). With the input's
   32 of top padding and 12 below, that is still ~4.5 lines of 18/1.4 — enough
   to write a paragraph in without the box dominating the panel. */
.rdk-field--area .rdk-field__box {
  height: calc(112 * var(--u));
}

/* Tracking is 0 on the label, its asterisk and the value alike (client,
   2026-08-28) — the design sets -0.18 desktop / -0.16 mobile on all three.
   Do not restore them from the node. */
.rdk-field__label {
  position: absolute;
  left: calc(20 * var(--u));
  top: 50%;
  translate: 0 -50%;
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(18 * var(--u));
  line-height: 1.2;  /* was `normal`, pinned 2026-09-21: that resolved to 1.2 from the brand face's own metrics, and the descender fix raises its declared descent. Stated so the fix cannot move this. */
  letter-spacing: 0;
  color: var(--rdk-dark);
  pointer-events: none;
  /* The label pivots at its LEFT edge, which is the edge it lines up on —
     a centred origin swings the start of the run in and out as it tips and
     the left margin looks ragged mid-flight. Same reason the menu links use
     it. */
  transform-origin: 0% 50%;
  /* This is the transition for coming back DOWN to rest. The float's own
     timing is stated on the `:has()` rule below, so the two directions can
     differ — the return is quicker and only just overshoots, because nothing
     is arriving, it is only letting go. */
  transition: translate 0.34s cubic-bezier(0.34, 1.32, 0.64, 1),
              font-size 0.26s cubic-bezier(0.33, 1, 0.68, 1),
              color 0.2s ease;
}

/* The textarea's label never centres — there is no single line for it to be
   centred on, so it sits at the top from the start. Note this puts its centre
   at ~38u, within 2u of where a single-line field's resting label sits, so the
   two read as level even though they are anchored differently. */
.rdk-field--area .rdk-field__label {
  top: calc(26 * var(--u));
  translate: 0 0;
}

/* The asterisk stays 18 even when the label shrinks to 14 — Figma sets it as
   its own 18px run inside the label (node 163:7033), and letting it scale with
   the label is the most visible thing that was wrong here. */
.rdk-field__label i {
  font-size: calc(18 * var(--u));
  font-style: normal;
  letter-spacing: 0;
  color: var(--rdk-green);
}

/* The floated size is a FLAT 14px, not `14 * --u` (client, 2026-08-28: it was
   too small when it reduced). --u is `min(100vw/1600, 1px)`, so the scaled
   version only ever hits 14 at 1600 and above — at 1440 it was 12.6 and at
   1280 11.2. The label sits in the 4..23 band of the box at every width, so it
   still clears the value's 32 (x --u) top padding when the box scales down.
   NOTE the resting size is deliberately still scaled: flooring that too would
   push 18px type plus its padding out of a box that is itself `72 * --u`. */
/* The float overshoots and the label tips as it goes (client, 2026-08-28:
   make it less linear). Three things carry it, and they are deliberately NOT
   in step — moving as one rigid block is what made it read as a slide:

   - the LIFT overshoots its mark and settles (`back.out` as a cubic-bezier,
     the same gesture the menu links and the footer bubbles use);
   - the SHRINK is shorter than the lift, so the label is already at 14 while
     it is still settling — it reads as shrinking on the way up rather than
     scaling in place;
   - the TIP is a keyframe, not a transition, because a transition can only
     land on a value and hold it — a permanently crooked label is not what is
     wanted. It runs once and corrects.

   `animation-name` really does go none -> name here, which is the only thing
   that (re)starts a CSS animation: `:has()` turns the whole rule on and off
   with the state. A field that is FILLED stays floated when focus leaves, so
   it does not re-fire on every visit — only on actually arriving. */
.rdk-field:has(.rdk-field__input:focus) .rdk-field__label,
.rdk-field:has(.rdk-field__input:not(:placeholder-shown)) .rdk-field__label {
  translate: 0 calc(-50% - 19 * var(--u));
  font-size: 14px;
  transition: translate 0.46s cubic-bezier(0.34, 1.56, 0.64, 1),
              font-size 0.28s cubic-bezier(0.33, 1, 0.68, 1),
              color 0.2s ease;
  animation: rdk-label-tip 0.62s cubic-bezier(0.33, 1, 0.68, 1);
}

/* Two direction changes before it is level, which is what makes it read as
   weight rather than as a wiggle. The angles are small on purpose: this is a
   form label, so it should read as character and not as damage. */
@keyframes rdk-label-tip {
  0%   { rotate: 0deg; }
  30%  { rotate: -3.2deg; }
  62%  { rotate: 1.1deg; }
  84%  { rotate: -0.3deg; }
  100% { rotate: 0deg; }
}

/* It LIFTS as well as shrinking (client, 2026-08-28) — it used to shrink in
   place, which is the one field that did not behave like the rest. The -19 is
   the same rise the single-line fields take, and since the two rest at the
   same height it lands the floated label at the same height too. */
.rdk-field--area:has(.rdk-field__input:focus) .rdk-field__label,
.rdk-field--area:has(.rdk-field__input:not(:placeholder-shown)) .rdk-field__label {
  translate: 0 calc(-19 * var(--u));
  font-size: 14px;
}

/* 12 of top padding + a 14 cap-trimmed label + the 8 gap puts the value's box
   at 32 (163:7031). Satoshi Medium 18/1.4 at -0.18 tracking, ink #001407. */
.rdk-field__input {
  width: 100%;
  height: 100%;
  padding: calc(32 * var(--u)) calc(20 * var(--u)) calc(12 * var(--u));
  font-family: var(--rdk-font-body);
  /* 400, not the design's 500 (client, 2026-08-28). Satoshi is a variable
     face declared 300..900, so this is a real Regular rather than a
     synthesised one. */
  font-weight: 400;
  /* Node 163:7109: Satoshi Regular at `body/s` 16 / 24 / 0.16, ink 950. Taken
     as the TOKENS rather than as literals — 24 on 16 IS the scale's 1.5 and
     0.16 on 16 IS its 0.01em — so the run picks a step rather than declaring
     a size, which is what gives it the touch-tablet floor for free.

     THE TRACKING OVERRIDES THE 2026-08-28 "tracking 0 on the label, its
     asterisk and the value". That was set against a design carrying -0.18;
     this node carries +0.16, i.e. the other direction, so it is a new value
     rather than a re-application of the one that was turned down. The LABEL
     and its asterisk keep 0. */
  font-size: var(--rdk-body-s);
  line-height: var(--rdk-body-lh);
  letter-spacing: var(--rdk-body-track);
  color: var(--rdk-black-green);
  background: none;
  border: 0;
  border-radius: inherit;
  /* Hidden until the label has moved out of the way, so the two never overlap
     mid-transition. The caret comes with it on focus. */
  opacity: 0;
  transition: opacity 0.18s ease;
}

/* 52 was clearance for a label that stayed put. Now that it lifts, the value
   starts at the same 32 as every other field. */
.rdk-field--area .rdk-field__input {
  padding-top: calc(32 * var(--u));
  resize: none;
}

.rdk-field__input:focus {
  outline: none;
}

.rdk-field:has(.rdk-field__input:focus) .rdk-field__input,
.rdk-field__input:not(:placeholder-shown) {
  opacity: 1;
}

/* ---------- autofill ----------
   Chrome paints its OWN chip on an autofilled field and ignores `background`
   and `color` outright, so a white box with dark ink came back grey-blue with
   white text — measured off a real autofill at (182, 189, 203), which is
   Chrome's DARK-mode chip `rgba(70, 90, 126, 0.4)` over our white to within
   1/255.

   It is dark-mode because `css/base.css` declares `color-scheme: dark` on the
   root, which is load-bearing (it stops a light-mode browser painting the
   overscroll canvas white). So the page is telling Chrome the truth about the
   PAGE and the wrong thing about these controls, which are a white box with
   near-black ink. `color-scheme: light` on the input is that correction, and
   it also fixes the caret and the selection colour for free.

   That alone still leaves Chrome's LIGHT chip (#e8f0fe), so the background is
   painted over with an inset box-shadow — the one background-ish property the
   UA does not override — and the ink set with `-webkit-text-fill-color`,
   which is the only thing `color` gives way to here.

   THE TWO SELECTORS MUST BE SEPARATE RULES, NOT ONE COMMA LIST. An unknown
   pseudo-class invalidates the whole selector list it sits in, so Firefox
   (which has `:autofill` and not `:-webkit-autofill`) would drop both. */
.rdk-field__input {
  color-scheme: light;
}

.rdk-field__input:-webkit-autofill,
.rdk-field__input:-webkit-autofill:hover,
.rdk-field__input:-webkit-autofill:focus {
  -webkit-text-fill-color: var(--rdk-black-green);
  -webkit-box-shadow: inset 0 0 0 calc(200 * var(--u)) var(--rdk-white);
  box-shadow: inset 0 0 0 calc(200 * var(--u)) var(--rdk-white);
  caret-color: var(--rdk-black-green);
}

.rdk-field__input:autofill,
.rdk-field__input:autofill:hover,
.rdk-field__input:autofill:focus {
  -webkit-text-fill-color: var(--rdk-black-green);
  box-shadow: inset 0 0 0 calc(200 * var(--u)) var(--rdk-white);
  caret-color: var(--rdk-black-green);
  /* Firefox tints an autofilled field with a filter rather than a fill. */
  filter: none;
}

/* ---------- the error state (node 168:7189) ---------- */

/* An author `display` beats the UA's `[hidden] { display: none }`, so the
   attribute stops working the moment you set one — the warning triangles were
   showing under every field at rest. */
.rdk-field__error[hidden] {
  display: none;
}

.rdk-field__error {
  display: flex;
  align-items: center;
  gap: calc(7 * var(--u));
  margin: 0;
  padding-left: calc(4 * var(--u));
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(14 * var(--u));
  line-height: 1.2;  /* was `normal`, pinned 2026-09-21: that resolved to 1.2 from the brand face's own metrics, and the descender fix raises its declared descent. Stated so the fix cannot move this. */
  color: var(--rdk-white);
}

/* The lime warning triangle. A background rather than markup so the JS can go
   on setting `textContent` without wiping an icon element. */
.rdk-field__error::before {
  content: "";
  flex: none;
  width: calc(13.5 * var(--u));
  height: calc(12 * var(--u));
  background: url("../assets/img/contact/icon-warning.svg") no-repeat center / contain;
}

.rdk-field.is-invalid .rdk-field__box {
  outline-color: var(--rdk-error);
}

/* Both runs go red — the asterisk drops its green in this state. */
.rdk-field.is-invalid .rdk-field__label,
.rdk-field.is-invalid .rdk-field__label i {
  color: var(--rdk-error);
}

/* ---------- checkboxes ---------- */

.rdk-contact__checks {
  position: relative;
  display: flex;
  flex-direction: column;
  gap: calc(12 * var(--u));
}

.rdk-check {
  display: flex;
  align-items: center;
  gap: calc(16 * var(--u));
  cursor: pointer;
}

/* Visually hidden, not `display: none` — it has to stay focusable and
   reachable by assistive tech. */
.rdk-check__input {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

.rdk-check__box {
  flex: none;
  display: flex;
  align-items: center;
  justify-content: center;
  width: calc(32 * var(--u));
  height: calc(32 * var(--u));
  color: var(--rdk-dark);
  background: var(--rdk-white);
  border-radius: calc(6.6 * var(--u));
  transition: scale 0.15s ease, outline-color 0.2s ease;
  outline: calc(2 * var(--u)) solid transparent;
  outline-offset: calc(-2 * var(--u));
}

.rdk-check__box svg {
  width: calc(15.87 * var(--u));
  height: calc(15.87 * var(--u));
  opacity: 0;
  transition: opacity 0.15s ease, scale 0.15s ease;
  scale: 0.6;
}

.rdk-check__input:checked + .rdk-check__box svg {
  opacity: 1;
  scale: 1;
}

.rdk-check:hover .rdk-check__box,
.rdk-check__input:focus-visible + .rdk-check__box {
  scale: 1.08;
}

.rdk-check.is-invalid .rdk-check__box {
  outline-color: var(--rdk-error);
}

.rdk-check__label {
  font-family: var(--rdk-font-body);
  font-weight: var(--rdk-body-weight);
  font-size: var(--rdk-body-s);
  line-height: var(--rdk-body-lh);
  letter-spacing: var(--rdk-body-track);
  color: var(--rdk-white);
  opacity: 0.9;
}

.rdk-check__label a {
  text-decoration: underline;
}

.rdk-check.is-invalid .rdk-check__label {
  color: var(--rdk-error);
}

/* ---------- submit ----------
   Two joined shapes, one button. The pill grows on hover and that growth
   shoves the chip along, because they are flex siblings — the same gesture as
   the case studies' "View project", in its CSS-only form. */

.rdk-contact__submit {
  display: inline-flex;
  /* Centre, not stretch: stretching made the chip take the pill's height and
     come out an oval. It is a fixed 80x80 circle in the design (163:7081). */
  align-items: center;
  align-self: flex-start;
  padding: 0;
  color: var(--rdk-white);
  background: none;
  border: 0;
  cursor: pointer;
}

.rdk-contact__submit-label,
.rdk-contact__submit-chip {
  display: flex;
  align-items: center;
  justify-content: center;
  border: calc(4 * var(--u)) solid var(--rdk-lime);
  border-radius: 999px;
}

/* The pill's padding is what shoves the chip — they are flex siblings, so the
   growth is layout and nobody has to measure it. `--grow-travel` and `--grow`
   come from js/btn-shove.js. NOTE the chip is pushed by BOTH sides growing,
   so its measured travel is twice the number here. */
.rdk-contact__submit-label {
  padding: calc(26 * var(--u))
           calc((56 + var(--grow-travel, 9.5) * var(--grow, 0)) * var(--u));
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(23 * var(--u));
  line-height: 1.2;
  letter-spacing: calc(-0.23 * var(--u));
}

/* 80 x 80, explicitly. Both dimensions have to be set or the chip inherits
   the pill's height and stops being a circle.

   The 4u overlap in the design: their borders merge rather than doubling.

   `--push` is the chip's own offset BEYOND the layout shove, in px, and
   `--squish` is how hard it came back and hit the pill — it squashes along the
   axis of impact and bulges across it. scaleY only reaches about 1.04, so it
   still reads as deformation rather than as the circle growing, which the
   client ruled out on the case studies. */
.rdk-contact__submit-chip {
  flex: none;
  width: calc(80 * var(--u));
  height: calc(80 * var(--u));
  margin-left: calc(-4 * var(--u));
  translate: calc(var(--push, 0) * 1px) 0;
  scale: calc(1 - 0.16 * var(--squish, 0)) calc(1 + 0.11 * var(--squish, 0));
}

.rdk-contact__submit-chip svg {
  width: calc(11.01 * var(--u));
  height: calc(17.78 * var(--u));
}

.rdk-contact__submit[disabled] {
  opacity: 0.6;
  cursor: progress;
}

.rdk-contact__status {
  letter-spacing: var(--rdk-body-track);
  margin: 0;
  font-family: var(--rdk-font-body);
  font-weight: var(--rdk-body-weight);
  font-size: var(--rdk-body-s);
  line-height: var(--rdk-body-lh);
  color: var(--rdk-white);
}

.rdk-contact__status:empty {
  display: none;
}

/* ---------- the confirmation (node 960:107407) ----------
   IT IS DRAWN 1:1, NOT THE 0.937 THIS FILE USUALLY CARRIES, and that was
   checked rather than assumed: the close button comes back 52, the frame's
   padding 64 / 64 / 72 and the title at Display/L 88 — the three values the
   shipped panel already has. So every number below is the node's own.

   It replaces `__head` AND the form inside the SAME panel, so the green, the
   26 radius, the padding and the close button are all the ones already there;
   `__inner`'s own `gap: 28` IS the node's frame gap. */

/* AN AUTHOR `display` BEATS THE UA'S `[hidden] { display: none }`, so the
   attribute is inert on all three of these — each is a flex box. Without this
   the form went on rendering under the confirmation with the attribute
   perfectly set, and the panel stayed at the form's full 852: the same trap
   this file already records against `.rdk-field__error`, and css/latest.css
   against the featured post. Fourth instance in the project. */
.rdk-contact__done[hidden],
.rdk-contact__head[hidden],
.rdk-contact__form[hidden] {
  display: none;
}

.rdk-contact__done {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: calc(40 * var(--u));
  width: 100%;
}

.rdk-contact__done-head {
  display: flex;
  flex-direction: column;
  gap: calc(16 * var(--u));
  width: calc(520 * var(--u));
}

.rdk-contact__title:focus-visible {
  outline: max(2px, calc(3 * var(--u))) solid var(--rdk-lime);
  outline-offset: max(4px, calc(6 * var(--u)));
}

/* THE NODE SETS 18 / 1.4 / 0.09px AND THE LEADING AND TRACKING ARE NOT
   APPLIED. `--rdk-body-m` is already 18, and the body scale's whole point is
   that a new run PICKS A STEP rather than declaring its own leading — which
   is also what gives this run the touch-tablet floor for free. The 410.136
   measure IS the node's. */
.rdk-contact__done-blurb {
  margin: 0;
  max-width: calc(410.136 * var(--u));
  font-family: var(--rdk-font-body);
  font-weight: var(--rdk-body-weight);
  font-size: var(--rdk-body-m);
  line-height: var(--rdk-body-lh);
  letter-spacing: var(--rdk-body-track);
  color: var(--rdk-white);
  opacity: 0.9;
}

.rdk-contact__wait {
  width: calc(417.793 * var(--u));
  max-width: 100%;
}

/* 16.379 on 22px type is Figma's cap trim, so it is carried as the RATIO the
   node draws (0.7445) rather than as a length — a length would go out of step
   with the font size's own floor on a small screen. */
.rdk-contact__wait-label,
.rdk-contact__wait-link {
  font-family: var(--rdk-font-brand);
  font-weight: 700;
  font-size: calc(22 * var(--du));
  line-height: 0.7445;
  letter-spacing: calc(-0.44 * var(--du));
}

/* --rdk-lime-bright, not --rdk-lime: the node names Colors/Redonk Lime Bright
   and the two are genuinely different (see css/tokens.css). */
.rdk-contact__wait-label {
  margin: 0;
  color: var(--rdk-lime-bright);
}

/* The two rules are NOT the same rule, which the screenshot alone does not
   show: the exported SVGs are a 2px white line above the first link and a 1px
   white at 30% between the two. Drawn as borders on the items, so the 20 gap
   the node has on either side of each is `margin-top` + `padding-top`. */
.rdk-contact__wait-list {
  margin: calc(20 * var(--u)) 0 0;
  padding: 0;
  list-style: none;
}

/* `display: flex` so the item has no LINE BOX. The link is inline-flex, and
   an inline-level box sits on a baseline — so the line box adds the font's
   descender space under it and each row came out ~7.75 taller than the node's,
   which put the panel 22 over its designed 461. The same reading-the-box
   mistake the work index's row records, one level down. */
.rdk-contact__wait-list li {
  display: flex;
  padding-top: calc(20 * var(--u));
  border-top: calc(2 * var(--u)) solid var(--rdk-white);
}

.rdk-contact__wait-list li + li {
  margin-top: calc(20 * var(--u));
  border-top-width: calc(1 * var(--u));
  border-top-color: rgba(255, 255, 255, 0.3);
}

/* `flex-end`, not `center`: the node aligns the arrow to the BOTTOM of the
   text (`items-end`), and its own SVG bleeds 2.121 above its 4 x 11 layer box
   and 0 below — so a bottom-aligned 13.1213-tall element puts the layer
   exactly where the node draws it. */
.rdk-contact__wait-link {
  display: inline-flex;
  align-items: flex-end;
  /* THE GAP IS THIS LINK'S PILL. js/btn-shove.js kicks `--grow` on hover and
     the gap opens by `--grow-travel`, which shoves the arrow along by layout —
     the same two-body model the buttons use, with the gap standing in for the
     padding there is no pill to open. See LINK_SHOVE in js/contact.js. */
  gap: calc((12 + var(--grow-travel, 0) * var(--grow, 0)) * var(--u));
  color: var(--rdk-white);
  text-decoration: none;
}

.rdk-contact__wait-link:hover,
.rdk-contact__wait-link:focus-visible {
  color: var(--rdk-lime-bright);
}

/* A CARET'S ELEMENT SIZE IS THE SIZE THE ASSET RENDERS AT, NOT THE BOX THE
   NODE REPORTS. This one comes back as a 4 x 11 layer where its own SVG export
   carries the 3-wide square-cap stroke and measures 8.57347 x 13.1213 — the
   trap css/cases.css records against the drag pill's chevrons. The node's own
   `inset-[-19.28%_-61.3%_0_-53.03%]` is that bleed, and it agrees: 4 x 2.1433
   is 8.5732 and 11 x 1.1928 is 13.1208.

   IT IS A SECOND GLYPH, NOT A RESIZE OF THE PILL CHIP'S (client, 2026-09-18).
   The 11.0115 x 17.7803 caret the About CTA, the services CTA, the project
   page and this dialog's own Submit chip all share is untouched — that one
   sits inside an 80 circle, this one beside a 22 line of text. Two shapes for
   two jobs; do not collapse them. */
.rdk-contact__wait-link svg {
  flex: none;
  width: calc(8.57347 * var(--du));
  height: calc(13.1213 * var(--du));
  /* The arrow's own mass, beyond the layout shove. IN PX, because `--push` is
     an impulse in px/sec integrated over seconds and not a fraction of
     anything — the trap js/btn-shove.js's own header records. */
  translate: calc(var(--push, 0) * 1px) 0;
}

/* ---------- the confirmation's artwork ----------
   HE AND HIS SHADOW ARE ONE BOX (client, 2026-09-18: the shadow has to move
   with him). The group's own bounds ARE his bounds — the shadow sits wholly
   inside them, blur and all — so the wrapper takes his box and the shadow is
   placed once inside it. Nothing that moves the pair needs two numbers again.

   THE CLIENT'S COMBINED EXPORT COULD NOT BE USED, and the reason is worth
   keeping: frame 964:112165 has no fill of its own, so Figma composited its
   CANVAS behind it — the PNG comes back with opaque #1E1E1E at all four edges
   and no alpha at all, which on a green panel is a grey rectangle. The same
   ancestor-chain trap tools/clean-logo.py exists for. Two assets with real
   alpha in one wrapper gets the same result, keeps the shadow a 3.8 KB
   resolution-independent SVG, and keeps him at 2x.

   The wrapper is a child of the PANEL, not of `__inner`, which is the scroll
   container: an absolutely placed child there counts toward its scrollable
   overflow, the trap css/cases.css records against the thumbnail strip. The
   panel's own `overflow: clip` is what crops his ears, as the node draws it.

   ANCHORED BY THE BOTTOM, where the node states a top. He is standing on a
   floor, so bottom-anchoring keeps his feet registered to it if the copy above
   ever wraps to another line, where a `top` would slide him down and off. His
   top is -34.75 on a 455.491 box, so his BOTTOM edge is at 420.74, which is
   40.26 up from the node's 461 panel foot. (`bottom` is the gap UNDER the
   element, not the leftover height — deriving it as 461 - 34.75 - 455.491 put
   him 70 too low with his ears uncropped.) */
.rdk-contact__donk {
  position: absolute;
  left: calc(481.66 * var(--u));
  bottom: calc(40.26 * var(--u));
  width: calc(330.479 * var(--u));
  height: calc(455.491 * var(--u));
  pointer-events: none;
  /* No `transition` — js/contact.js TWEENS this opacity, and a transition on
     the same property turns every frame the tween writes into a 0.25s ease of
     its own, which fights it. The rule below is what shows him under reduced
     motion and with no GSAP, where nothing tweens. */
  opacity: 0;
}

.rdk-contact__donk img {
  position: absolute;
  max-width: none;
}

/* The full box — the group's bounds are his. */
.rdk-contact__donk-img {
  inset: 0;
  width: 100%;
  height: 100%;
}

/* The SVG carries its own blur bleed, so the FILE is 261.498 x 58.4075 where
   the LAYER the node reports is 236.057 x 36.988 — a caret's element size is
   the size the asset renders at, the rule css/cases.css records against the
   drag pill's chevrons.

   EVERY NUMBER HERE IS A SHARE OF THE WRAPPER, not a length in design units,
   which is what makes the pair ONE object at any size: the phone draws him
   176 wide and the shadow follows on its own. In `--u` it would have stayed
   261.498 * 0.24 = 63px there — the collapsing unit, on a shadow. The four
   are 36.38/330.479, 2.02/455.491, 261.498/330.479 and 58.4075/455.491, so
   at `--u: 1` they resolve to exactly the node's own numbers. */
.rdk-contact__donk-shadow {
  left: 11.009%;
  bottom: 0.4435%;
  width: 79.13%;
  height: 12.823%;
}

.rdk-contact.is-done .rdk-contact__donk {
  opacity: 1;
}

/* The node draws no speech bubble in this state — he has it instead. */
.rdk-contact.is-done .rdk-contact__bubble {
  opacity: 0;
}

/* ---------- mobile ----------
   No frame for this yet; the panel simply becomes the screen with the design's
   proportions scaled off the viewport. */
/* ---------- phone ----------
   Nearly every length in this component is in `--u`, which is 100vw/1600 — so
   at 430 wide it resolves to 0.27px per unit and the whole dialog collapses:
   the field boxes came out 19px tall against their 16px labels, the asterisk
   4.8px, the close button 14px and the checkboxes 8.6px. The previous block
   overrode a handful of font sizes and put its field height on `.rdk-field`,
   which is the flex COLUMN — so the column grew and the white box inside it
   stayed 19px, leaving the labels floating over empty green and the submit
   button pushed off the bottom.

   So this restates everything the component sizes, in straight px like every
   other mobile block here. There is no phone frame for this dialog; the
   proportions follow the design's own and the type follows the site's mobile
   scale. */
/* AND A TOUCH TABLET TAKES IT TOO (client, 2026-09-17: "on tablet portrait,
   the Let's talk dialog seems too small"). It was the collapsing unit again,
   in the one component that had no answer above 767 — and the two things that
   HAVE been floored are what made it obvious. Measured at 820 on a real
   tablet's gates: the title is 59.84 (`--du`) and the blurb 17 (the body
   scale), correct and consistent with every other page — sitting in a panel
   drawn at HALF size around them. Panel 450 x 608, field boxes 37 tall,
   9.2px inputs and labels, a 27px close button, a 16px checkbox, a 41px chip.
   So the dialog read as a small box with a big headline crammed into it.

   THE 9.2px INPUT IS THE PART THAT IS NOT MERELY UGLY: iPad Safari zooms the
   page in on a focused input under 16px, and there is no way back out of that
   zoom inside a fixed dialog. The phone block already floors it at 16 for that
   exact reason, and the reason is a property of mobile Safari, not of the
   phone. The close button and the checkbox are the same argument in the other
   direction — 27 and 16 against the 44 a thumb needs.

   GATED ON THE POINTER AND NOT THE ORIENTATION, the split the body scale and
   the button floors already use: a LANDSCAPE tablet has the same problem less
   severely (at 1180 the input is 13.3px, still under the zoom bar) and a tall
   narrow desktop window has none. No desktop can match `hover: none`, so the
   gate cannot reach one. */
@media (max-width: 767px),
       (min-width: 768px) and (max-width: 1024px),
       (min-width: 768px) and (hover: none) {
  /* The confirmation, at the narrow treatment. Straight px like every other
     rule in this block: `--u` is 100vw/1600, so the node's 22 resolves to 5.4
     at 390 and its 417.793 measure to 102. There is no phone frame for this
     state, so these follow the panel's own narrow proportions — the title
     takes the block's existing 52, the links the menu's own step down. */
  .rdk-contact__done {
    gap: 32px;
  }

  .rdk-contact__done-head {
    gap: 12px;
    width: 100%;
  }

  .rdk-contact__done-blurb {
    max-width: none;
  }

  .rdk-contact__wait {
    width: 100%;
  }

  .rdk-contact__wait-label,
  .rdk-contact__wait-link {
    font-size: 18px;
    letter-spacing: -0.36px;
  }

  .rdk-contact__wait-list {
    margin-top: 16px;
  }

  .rdk-contact__wait-list li {
    padding-top: 16px;
    border-top-width: 2px;
  }

  .rdk-contact__wait-list li + li {
    margin-top: 16px;
    border-top-width: 1px;
  }

  /* THE GAP HAS TO BE RESTATED WITH THE SIZES. `8 * var(--u)` is 1.95px at
     390 — the collapsing-unit trap, and exactly the bug css/pill.css records
     against the services pills, where the mobile block overrode the pill's
     size and not the gap and they all but touched. The chevron read as
     glued to the word. */
  /* No hover here, so no shove — but the gap keeps the same shape as the
     desktop's rather than a flat 10, so a hybrid device that does fire one
     opens from 10 instead of snapping back to it. */
  .rdk-contact__wait-link {
    gap: calc(10px + var(--grow-travel, 0) * var(--grow, 0) * 1px);
  }

  .rdk-contact__wait-link svg {
    width: 7px;
    height: 10.7px;
  }

  /* HE GOES UNDER THE CONTENT (client, 2026-09-18), where the desktop puts
     him in the right-hand third of an 879 panel — there is no right-hand
     third at 390, and beside the copy he would cover it.

     He stays absolutely placed on the PANEL and `__inner` opens the room for
     him with its own bottom padding, rather than moving him into the flow:
     the flow he would join is `__inner`'s, which is the scroll container, and
     an absolutely placed child there counts toward its scrollable overflow.
     One DOM for both breakpoints. 307 is his 242.58 plus a 32 gap above him
     and the 32 the panel already had below — change one and change both.

     SCOPED TO THE STATE, and that is doing two jobs. The form is not standing
     on him and must not carry 307px of empty foot. And `.rdk-contact__inner`
     is set again LATER in this same block with the `padding` SHORTHAND, which
     resets `padding-bottom` at equal specificity — a bare rule here silently
     lost to it and Donk sat on top of the copy. `.is-done` is (0,2,0), so it
     wins wherever it sits in the file. */
  .rdk-contact.is-done .rdk-contact__inner {
    padding-bottom: 307px;
  }

  .rdk-contact__donk {
    left: 50%;
    bottom: 32px;
    width: 176px;
    /* 176 * 455.491 / 330.479. Stated rather than `auto` because the shadow
       inside is a share of this box. */
    height: 242.58px;
    translate: -50% 0;
  }
  /* SIZED FROM THE VISUAL VIEWPORT, not from `svh`. iOS Safari does not shrink
     the layout viewport when the keyboard opens, so `100svh` stays the whole
     screen and the keyboard just covers the bottom of the panel — the field
     being typed into and the submit button both end up underneath it.
     js/contact.js writes `--vv-h` / `--vv-top` from `window.visualViewport`
     while the dialog is open; the `svh` fallback is what a browser without
     the API gets, i.e. exactly the old behaviour. */
  .rdk-contact__panel {
    top: calc(12px + var(--vv-top, 0px));
    right: 12px;
    left: 12px;
    width: auto;
    max-width: none;
    max-height: calc(var(--vv-h, 100svh) - 24px);
    border-radius: 28px;
  }

  /* The form is far taller than a phone, so this is the ONLY thing keeping
     the submit button reachable. It is `overflow-y: auto` on the desktop rule
     already; what matters here is that the padding leaves room for the close
     button and that nothing else claims the height. */
  .rdk-contact__inner {
    align-items: stretch;
    gap: 20px;
    /* Top was 72 (client, 2026-08-28: take ~24 off). 48 still clears the
       close button, which ends at 58 — but they never meet anyway, the title
       running to ~250 against the button's left edge at ~308. */
    padding: 48px 20px 32px;
    /* Or a flick past either end of the form chains out to the page behind
       and rubber-bands the whole dialog. The root is `overflow: clip` while
       the dialog is open, so nothing actually scrolls — it just wobbles. */
    overscroll-behavior: contain;
    /* Keeps a field scrolled into view clear of the close button, which
       floats over this scroll region rather than scrolling with it. */
    scroll-padding: 64px 0 24px;
  }

  .rdk-contact__close {
    top: 14px;
    right: 14px;
    width: 44px;          /* a real tap target; it was 14px square */
    height: 44px;
    border-radius: 14px;
  }

  .rdk-contact__close svg {
    width: 16px;
    height: 16px;
  }

  /* The designed break belongs to the desktop measure. At 390 the first
     sentence already wraps on its own, so forcing another break after it
     leaves a short line in the middle of the paragraph and the rag reads as
     broken rather than set. Dropping it lets the two sentences run on —
     `text-wrap: pretty` (set on body) then handles the rag. */
  .rdk-contact__blurb-break {
    display: none;
  }

  .rdk-contact__head {
    width: 100%;
    gap: 10px;
    margin-bottom: 0;
  }

  .rdk-contact__title {
    font-size: 52px;
    letter-spacing: -1.56px;
  }

  .rdk-contact__blurb {

  }

  .rdk-contact__form {
    gap: 18px;
  }

  /* THE FIELD-TO-FIELD GAP IS SET IN TWO PLACES and both have to match, or
     the stacked name/company pair spaces differently from the rest of the
     list. `.rdk-contact__fields` is the wrapper around the five fields and
     `.rdk-contact__row` is the name+company pair, which stacks here. Missing
     the wrapper left it at `16 * --u` — 4.3px at 430 — while the pair kept
     18, which read as extra space after the name field. */
  .rdk-contact__fields,
  .rdk-contact__row {
    gap: 12px;
  }

  /* Two side-by-side fields do not fit a phone. */
  .rdk-contact__row {
    flex-direction: column;
  }

  /* The gap between the white box and its error message — NOT a height. The
     height belongs to `__box`; putting it here is what broke the last pass. */
  .rdk-field {
    gap: 8px;
  }

  .rdk-field__box {
    height: 62px;
    border-radius: 14px;
    outline-width: 2px;
    outline-offset: -2px;
  }

  .rdk-field--area .rdk-field__box {
    height: 150px;
  }

  .rdk-field__label {
    left: 16px;
    font-size: 16px;
    letter-spacing: 0;
  }

  .rdk-field__label i {
    font-size: 16px;
    letter-spacing: 0;
  }

  .rdk-field--area .rdk-field__label {
    top: 20px;
  }

  /* The float, restated: the label rises clear of the value and shrinks. */
  .rdk-field:has(.rdk-field__input:focus) .rdk-field__label,
  .rdk-field:has(.rdk-field__input:not(:placeholder-shown)) .rdk-field__label {
    translate: 0 calc(-50% - 15px);
    font-size: 12px;
  }

  /* `translate: 0 0` is NOT redundant. The desktop rule now lifts this label
     by `-19 * --u`, and --u on a phone is ~0.27px — so without restating it
     the mobile label would take a small unasked-for rise through the cascade.
     The change was desktop-only. */
  .rdk-field--area:has(.rdk-field__input:focus) .rdk-field__label,
  .rdk-field--area:has(.rdk-field__input:not(:placeholder-shown)) .rdk-field__label {
    translate: 0 0;
    font-size: 12px;
  }

  /* 16px is a FLOOR, not a preference: iOS Safari zooms the page in when a
     focused input is under 16px, and there is no way back out of that zoom
     inside a fixed dialog. */
  .rdk-field__input {
    padding: 26px 16px 8px;
    /* STAYS A HARD 16, NOT `--rdk-body-s`, which is 15 here. iOS Safari zooms
       the page in on a focused input under 16px and there is no way back out
       of that zoom inside a fixed dialog — not a taste decision. */
    font-size: 16px;
    letter-spacing: var(--rdk-body-track);
  }

  .rdk-field--area .rdk-field__input {
    padding-top: 44px;
  }

  .rdk-field__error {
    gap: 6px;
    padding-left: 4px;
    font-size: 13px;
  }

  .rdk-field__error::before {
    width: 12px;
    height: 11px;
  }

  .rdk-contact__checks {
    gap: 12px;
  }

  .rdk-check {
    gap: 12px;
  }

  .rdk-check__box {
    width: 26px;
    height: 26px;
    border-radius: 6px;
    outline-width: 2px;
    outline-offset: -2px;
  }

  .rdk-check__box svg {
    width: 13px;
    height: 13px;
  }

  .rdk-check__label {

  }

  /* The pill and the chip keep the shove — `--grow-travel` and `--push` are
     driven by js/btn-shove.js and are in px already — but their own sizes are
     in `--u` and have to come back up. */
  .rdk-contact__submit-label,
  .rdk-contact__submit-chip {
    border-width: 3px;
  }

  .rdk-contact__submit-label {
    padding: 17px calc(30px + var(--grow-travel, 9.5) * var(--grow, 0) * 1px);
    font-size: 18px;
    letter-spacing: -0.18px;
  }

  .rdk-contact__submit-chip {
    width: 56px;
    height: 56px;
    margin-left: -3px;
  }

  .rdk-contact__submit-chip svg {
    width: 9px;
    height: 14.5px;
  }

  .rdk-contact__status {
  }

  /* The bubble hangs from the panel's top edge, kept at the design's own
     proportion of the panel rather than re-placed: on desktop it is 524.3 of
     an 879 panel starting at 346.83, i.e. 59.6% wide at 39.5% across. Stated
     as percentages it holds that at any phone width, and `height: auto` keeps
     the aspect from the file, which is already cropped to the part the panel
     shows.

     It is a child of the PANEL, not of the scrolling inner, so it stays put
     while the form scrolls under it — the same as desktop. `.rdk-contact__form`
     is `position: relative` for exactly this reason. */
  .rdk-contact__bubble {
    left: 39.5%;
    width: 59.6%;
    height: auto;
  }
}

/* AND THE FOUR NUMBERS A 390-FRAME VALUE GETS WRONG ON AN 820 SCREEN. It has
   to come AFTER the block above — a media query adds no specificity, so at
   equal weight source order decides. Same arrangement as the footer's pair.

   THE PANEL IS NOT FULL-BLEED HERE, AND THAT IS THE ONE REAL DECISION. On a
   phone the dialog is the screen because there is no room to be anything else;
   on a tablet there is, and the "Let's talk" button it grows out of is still
   in the nav's top-right corner at this width (the CTA is only hidden below
   768). So it keeps the desktop's anchor and morphs out of the real button
   rather than expanding into a sheet that starts nowhere.

   620 IS SET BY THE CONTENT, not picked: it leaves 556 inside the padding,
   which is two 272 fields plus their 12 gap — so the name/company pair stays
   a PAIR here, where the phone stacks it. `min()` caps it, so a 1024 portrait
   tablet gets the same dialog rather than a 1000-wide single column. */
@media (min-width: 768px) and (max-width: 1024px),
       (min-width: 768px) and (hover: none) {
  .rdk-contact__panel {
    top: calc(24px + var(--vv-top, 0px));
    right: 24px;
    left: auto;
    width: min(620px, 100vw - 48px);
    max-height: calc(var(--vv-h, 100svh) - 48px);
  }

  /* The title stays on `--du` rather than taking the phone's flat 52: 88 at
     the tablet floor is 59.84, which is what every other section headline on
     the site renders at this width. It fits the 556 measure on one line. */
  .rdk-contact__title {
    font-size: calc(88 * var(--du));
    letter-spacing: calc(-3.52 * var(--du));
  }

  /* Two fields side by side DO fit 556. */
  .rdk-contact__row {
    flex-direction: row;
  }
}

/* THE BLURB TAKES THE SMALLER SIZE ON THE PHONE (client, 2026-09-17), which
   is what the tablet band beside it already renders — the portrait 1px-down
   block in css/tokens.css puts `--rdk-body-m` at 16 there while the phone
   token holds it at 17, so one dialog treatment was showing two sizes.

   THIS IS THE ONE PER-RUN OVERRIDE THE BODY SCALE WARNS ABOUT, and it is
   flagged rather than hidden: "a new body run PICKS A STEP, it does not
   declare a size". 16 is not a step the phone has — its five are 18 / 17 /
   17 / 15 / 14 — so the alternative that stays on the scale is
   `var(--rdk-body-s)` at 15, one whole step down rather than one pixel.
   Ask for that instead if the exact size matters less than the rule.

   It must come AFTER the shared block above: a media query adds no
   specificity, so at equal weight source order is the whole of it. */
@media (max-width: 767px) {
  .rdk-contact__blurb {
    font-size: 16px;
  }
}

@media (prefers-reduced-motion: reduce) {
  /* The shove is a spring on the ticker, so it cannot be turned off in the
     stylesheet — js/contact.js does not bind it at all under reduced motion.
     Neutralise the properties in case one was already written. */
  .rdk-contact__submit-chip {
    translate: none;
    scale: 1;
  }

  .rdk-contact__close,
  .rdk-field__label,
  .rdk-field__input,
  .rdk-check__box,
  .rdk-check__box svg,
  .rdk-contact__submit-label,
  .rdk-contact__submit-chip {
    transition: none;
  }

  /* The label's tip is a keyframe, so `transition: none` does not reach it. */
  .rdk-field:has(.rdk-field__input:focus) .rdk-field__label,
  .rdk-field:has(.rdk-field__input:not(:placeholder-shown)) .rdk-field__label {
    transition: none;
    animation: none;
  }
}
