/* ==========================================================================
   Dice Codes — Design System
   Prototype stylesheet. In production this compiles to one file, inlined
   above the fold and deferred below it.
   ========================================================================== */

/* --- Tokens ------------------------------------------------------------- */

:root {
  /* Monochrome. Black, white and grey only. Hierarchy is carried by
     contrast, weight and space rather than by hue. */
  --ink:        #0E0E10;   /* page ground */
  --ink-raise:  #141416;
  --panel:      #1A1A1D;   /* cards */
  --panel-2:    #232327;   /* card hover */
  --line:       #2C2C31;   /* hairlines */
  --grid:       rgba(255, 255, 255, 0.034);   /* graph paper */
  --grid-size:  52px;

  --accent:       #FFFFFF;              /* the only accent is pure white */
  --accent-dim:   #B8B8BC;
  --accent-ghost: rgba(255, 255, 255, 0.08);

  --bone:       #F2F2F0;   /* primary text */
  --mute:       #9B9BA0;   /* secondary text */
  --faint:      #8C8C93;   /* labels, meta, breadcrumbs.
                              4.69:1 on the darkest card, so it clears AA
                              on the page ground and on every panel */

  --display: "Space Grotesk", "Segoe UI", system-ui, sans-serif;
  --body:    "Chivo", "Segoe UI", system-ui, sans-serif;

  /* Perfect fourth scale, fluid */
  --t-xs:   0.78rem;
  --t-sm:   0.92rem;
  --t-base: 1.0625rem;
  --t-md:   clamp(1.25rem, 1.05rem + 0.6vw, 1.5rem);
  --t-lg:   clamp(1.6rem, 1.25rem + 1.4vw, 2.35rem);
  --t-xl:   clamp(2.1rem, 1.4rem + 2.9vw, 3.9rem);
  --t-2xl:  clamp(2.6rem, 1.2rem + 5.2vw, 5rem);
  --t-huge: clamp(4rem, 0.5rem + 14vw, 14rem);

  --gutter: clamp(1.25rem, 4vw, 5.5rem);
  --rail:   3.25rem;              /* left gutter rail width */
  --max:    82rem;

  --step: clamp(4.5rem, 9vw, 9.5rem);   /* vertical section rhythm */

  --ease: cubic-bezier(0.22, 0.61, 0.36, 1);

  /* Glass. One definition, used by every panel in the theme, so the header,
     the services menu, the mobile bar and every card on a page are visibly
     the same material rather than six near-misses.

     The tint is the substance and the sheen is the highlight on top of it.
     Getting that the wrong way round is what made the services panel
     see-through: a white wash over a blur does not hide dark-on-light type,
     it just makes a bright smear of it. */
  --glass-tint:    rgba(20, 20, 23, 0.55);   /* in-page cards */
  --glass-tint-up: rgba(17, 17, 20, 0.86);   /* floating overlays */
  --glass-sheen:   linear-gradient(180deg, rgba(255, 255, 255, 0.075) 0, rgba(255, 255, 255, 0.022) 170px);
  --glass-line:    rgba(255, 255, 255, 0.10);
  --glass-line-lit: rgba(255, 255, 255, 0.2);
  --glass-lip:     inset 0 1px 0 rgba(255, 255, 255, 0.14);
  --glass-blur:    blur(24px) saturate(170%);
}

/* --- Reset -------------------------------------------------------------- */

*, *::before, *::after { box-sizing: border-box; }

html {
  /* Smooth scrolling is applied to in-page anchors only. Setting it on the
     root makes every programmatic jump animate, including the ones a browser
     does on load and on back navigation, and that animation reads as the page
     being slow to respond rather than as a nicety. */
  -webkit-text-size-adjust: 100%;
}

@media (prefers-reduced-motion: no-preference) {
  :has(:target) { scroll-behavior: smooth; }
}

body {
  margin: 0;
  background-color: var(--ink);
  color: var(--bone);
  font-family: var(--body);
  font-size: var(--t-base);
  line-height: 1.65;
  font-synthesis-weight: none;
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;

  /* Graph paper. This is a studio that measures things, and the page prints
     its own readings in the hero, so it belongs on ruled ground. Kept at
     roughly 3% white: legible as a texture at arm's length, invisible as a
     pattern. Two gradients rather than an image, so it costs no request. */
  background-image:
    linear-gradient(var(--grid) 1px, transparent 1px),
    linear-gradient(90deg, var(--grid) 1px, transparent 1px);
  background-size: var(--grid-size) var(--grid-size);
  background-position: center top;
}

img, svg, canvas { display: block; max-width: 100%; }
img { height: auto; }

a { color: inherit; text-decoration: none; }

h1, h2, h3, h4 {
  font-family: var(--display);
  font-weight: 700;
  line-height: 1.02;
  letter-spacing: -0.025em;
  margin: 0;
  text-wrap: balance;
}

p { margin: 0 0 1.15em; max-width: 68ch; }

::selection { background: var(--accent); color: var(--ink); }

:focus-visible {
  outline: 2px solid var(--accent);
  outline-offset: 3px;
  border-radius: 2px;
}

/* --- Glass -------------------------------------------------------------- */
/* Two tiers, and the split is deliberate.

   .glass       Real backdrop-filter. Applied only where page content
                actually passes behind the element: the header, the mobile
                menu overlay, the floating contact button. backdrop-filter
                makes the compositor re-sample everything underneath on
                every frame, so it is rationed rather than sprayed around.

   .glass-flat  The same surface treatment with no filter, for panels
                sitting on a flat ground where there is nothing behind to
                blur. On a solid background the two are indistinguishable,
                and this one costs nothing.

   Both use a top-to-bottom fill plus an inset highlight on the top edge,
   which is what reads as a lit pane of glass rather than a grey box. */

/* Glass on a dark ground needs a dark base, not a white tint.
 *
 * This used to paint 1% white over the blur, which on this palette is
 * effectively nothing: the services panel was see-through and the page
 * headline read straight through it. Blur alone does not solve that. Blurring
 * large white type produces a bright smear, which is still bright. What
 * separates a pane from what is behind it is an opaque-enough base of the
 * surface colour, with the white gradient on top as a sheen rather than as
 * the substance. */
.glass,
.glass-flat {
  background-image: var(--glass-sheen);
  background-color: var(--glass-tint);
  border: 1px solid var(--glass-line);
  /* Inset highlight only. A large drop shadow is what sells a floating pane,
     but on a flat ground it is invisible and still costs a full blur pass per
     element per frame. With twenty of these on a page that was enough to stall
     compositing, so the expensive shadow is reserved for .glass below. */
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.14);
}

/* Real blur is reserved for chrome that is transient or short: the header
   strip, the mobile overlay, the services panel. The floating contact
   button is fixed and on screen the whole time, so blurring behind it would
   force a full-viewport re-sample every frame on top of the WebGL canvas.
   It uses the flat surface with a solid ground instead. */
.float-btn {
  background-color: rgba(28, 28, 32, 0.92);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    0 8px 22px rgba(0, 0, 0, 0.40);
}

.glass {
  /* Opaque enough that anything behind it is a texture rather than a word. */
  background-color: var(--glass-tint-up);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow:
    inset 0 1px 0 rgba(255, 255, 255, 0.14),
    0 18px 44px rgba(0, 0, 0, 0.55);
}

/* Without backdrop-filter there is no blur to help, so the pane goes almost
   solid rather than leaving the content behind it legible through the glass. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  :root {
    --glass-tint:    rgba(20, 20, 23, 0.92);
    --glass-tint-up: rgba(17, 17, 20, 0.97);
  }

  /* The header sets its own colour rather than the token, so it needs saying
     twice. Nav type over an unblurred page is unreadable otherwise. */
  .hdr { background-color: rgba(14, 14, 16, 0.97); }
}

/* Hover lifts the pane rather than recolouring it */
.glass-hover { transition: background-color 0.25s var(--ease), border-color 0.25s var(--ease); }
.glass-hover:hover {
  background-color: rgba(255, 255, 255, 0.055);
  border-color: rgba(255, 255, 255, 0.20);
}

/* Safari and Firefox without backdrop-filter fall back to an opaque panel,
   so chrome elements never turn illegible against scrolling content. */
@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .glass { background-color: rgba(20, 20, 23, 0.95); }
}

/* --- Layout primitives -------------------------------------------------- */

.wrap {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

/* Blocks are rendered straight into <main> by the block editor, with no
   wrapper of their own. That makes main the page container: it constrains and
   pads every block, and supplies the vertical rhythm that a .section used to.
   Blocks that carry their own .wrap, or are meant to run edge to edge, opt
   back out. Without this every block sits at viewport width with no gutter,
   which is what "the design is broken" looks like. */

#main > * {
  width: 100%;
  max-width: var(--max);
  margin-inline: auto;
  padding-inline: var(--gutter);
}

#main > .hero,
#main > .marquee {
  max-width: none;
  padding-inline: 0;
}

/* Rhythm. Each block is a section, so the gap between them is the section
   step. A section head owns the gap to whatever it introduces, so the pair
   stays together rather than drifting apart. */

#main > * + * { margin-block-start: var(--step); }

/* The breadcrumb row is part of the header that follows it, not a section of
   its own. Without this the rhythm rule put a full section step, up to 152px,
   between the trail and the page title, which is where the blank band under
   the breadcrumbs on posts was coming from. */
#main > .crumbs-row + * { margin-block-start: 0; }

/* A .section already carries a full step of padding top and bottom. Adding
   the rhythm margin beside one stacks two steps where one was intended, and
   next to a second padded section it becomes three: measured at 395px of dead
   space between the end of an article and its comment form. A section owns
   its own spacing, so the margin stands down on either side of one. */
#main > .section + *,
#main > * + .section { margin-block-start: 0; }

/* Note for anything added here later. An element directly under #main carries
   the page gutter, so if it also paints a background the paint shows as a band
   down each side of the content. That was the light strips beside the feature
   list and the stat row. Both are now grids of separate cards and paint
   nothing themselves, so the clip they needed has gone with them; a new block
   that paints its own panel needs background-clip: content-box, or the paint
   moved to an inner element. The call to action below is the worked example. */

/* The call to action is a painted panel rather than a full width band, so it
   has to sit inside the gutter rather than paint over it. Measured on a phone
   it was 375px wide with a 28px corner radius, which put its rounded corners
   off both edges of the screen. It now insets by a gutter on each side and
   carries its own internal padding. */
#main > .cta-band {
  width: calc(100% - 2 * var(--gutter));
  max-width: calc(var(--max) - 2 * var(--gutter));
  padding-inline: clamp(1.75rem, 5vw, 4rem);
}

/* The gutter was being applied twice: once by the rule above on the section,
   and again by the .wrap inside it. Measured at a 1733px viewport the content
   column came out 1035px wide where 1173 was available, and every page on the
   site was losing a gutter's width for no reason. The wrap inside an already
   padded section stands down. The hero and the marquee opt out of the outer
   padding, so their own wrap keeps doing the work. */
#main > *:not(.hero):not(.marquee) .wrap {
  max-width: none;
  padding-inline: 0;
}
#main > .head + * { margin-block-start: 0; }
#main > .marquee { margin-block: calc(var(--step) * 0.75); }
#main > .hero + * { margin-block-start: var(--step); }
/* The service links sit where the gauge used to, so they belong to the hero
   rather than reading as a section of their own. */
#main > .hero + .pills { margin-block-start: calc(var(--step) * 0.4); }

/* A run of prose under a head reads as one passage, not as separate sections. */
#main > p + p,
#main > p + figure,
#main > figure + p { margin-block-start: 1.1rem; }

.section { padding-block: var(--step); position: relative; }
.section--tight { padding-block: calc(var(--step) * 0.6); }
.section--flush-top { padding-top: 0; }

.skip {
  position: absolute;
  left: -9999px;
  top: 0;
  background: var(--accent);
  color: var(--ink);
  padding: 0.75rem 1.25rem;
  font-family: var(--display);
  font-weight: 700;
  z-index: 999;
}
.skip:focus { left: 1rem; top: 1rem; }

/* --- The brace: this brand's structural device -------------------------- */
/* Used instead of tracked all-caps eyebrows. Lowercase, braces in grey.   */

.brace {
  font-family: var(--display);
  font-size: var(--t-sm);
  font-weight: 500;
  color: var(--mute);
  letter-spacing: 0.01em;
  display: inline-flex;
  gap: 0.45em;
  margin-bottom: 1.4rem;
}
.brace::before { content: "{"; color: var(--faint); }
.brace::after  { content: "}"; color: var(--faint); }

/* Section head: heading left, supporting prose right */

.head {
  display: grid;
  grid-template-columns: minmax(0, 1.05fr) minmax(0, 0.95fr);
  gap: clamp(1.5rem, 4vw, 5rem);
  align-items: end;
  margin-bottom: clamp(2.5rem, 5vw, 4.5rem);
}
.head h2 { font-size: var(--t-xl); }
.head p  { color: var(--mute); margin: 0; font-size: var(--t-md); line-height: 1.5; }

@media (max-width: 780px) {
  .head { grid-template-columns: 1fr; align-items: start; gap: 1.25rem; }
}

/* --- Buttons ------------------------------------------------------------ */

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.6em;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-sm);
  letter-spacing: 0.005em;
  padding: 0.95em 1.7em;
  border-radius: 999px;
  border: 1px solid transparent;
  cursor: pointer;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease),
              border-color 0.2s var(--ease);
}

.btn--fill {
  background: var(--accent);
  color: var(--ink);
}
.btn--fill:hover { background: var(--bone); }

.btn--line {
  color: var(--bone);
}
.btn--line:hover { border-color: var(--accent); color: var(--accent); }

/* Outline button sitting on an inverted (white) surface */
.btn--on-light {
  border-color: rgba(14, 14, 16, 0.28);
  color: var(--ink);
}
.btn--on-light:hover { border-color: var(--ink); color: var(--ink); }

/* --- Header ------------------------------------------------------------- */

/* A floating capsule rather than a bar welded to the top of the window. It is
   inset from all three edges so the page shows around it, which is what makes
   it read as an object sitting over the content rather than a chrome bar
   bolted on. Measured from the reference: 16px from the top, inset a gutter
   each side, 16px radius, glass at 74% with a 22px blur. */

.hdr {
  position: fixed;
  top: 0.9rem;
  left: var(--gutter);
  right: var(--gutter);
  z-index: 60;
  max-width: calc(var(--max) - var(--gutter));
  margin-inline: auto;
  padding: 0.75rem 0.8rem 0.75rem 1.5rem;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  border-radius: 18px;
  border: 1px solid var(--glass-line);
  background-image: var(--glass-sheen);
  background-color: rgba(14, 14, 16, 0.72);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  box-shadow: 0 10px 30px -20px rgba(0, 0, 0, 0.85), var(--glass-lip);
  transition:
    left 0.4s var(--ease),
    right 0.4s var(--ease),
    max-width 0.4s var(--ease),
    padding 0.4s var(--ease),
    background-color 0.3s var(--ease),
    box-shadow 0.3s var(--ease);
}

/* On scroll it widens toward the edges and slims down, rather than simply
   changing colour. Proportions taken from the reference, which goes from
   1180px wide and 103px tall at rest to 1280 and 97 once it is stuck: the
   capsule reaches out and flattens as the page moves behind it. */
.hdr[data-stuck="true"] {
  left: calc(var(--gutter) * 0.5);
  right: calc(var(--gutter) * 0.5);
  max-width: var(--max);
  padding-block: 0.5rem;
  background-color: rgba(14, 14, 16, 0.88);
  box-shadow: 0 16px 40px -18px rgba(0, 0, 0, 1), var(--glass-lip);
}

/* Somebody who has asked for less motion gets the colour change and none of
   the resizing. */
@media (prefers-reduced-motion: reduce) {
  .hdr { transition: background-color 0.3s var(--ease); }
  .hdr[data-stuck="true"] {
    left: var(--gutter);
    right: var(--gutter);
    max-width: calc(var(--max) - var(--gutter));
    padding-block: 0.75rem;
  }
}

@supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
  .hdr { background-color: rgba(14, 14, 16, 0.96); }
}

.hdr[data-static="true"] { position: absolute; }

.logo {
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.32rem;
  letter-spacing: -0.045em;
  color: var(--bone);
  white-space: nowrap;
}
.logo b { font-weight: 700; color: var(--faint); }

.nav {
  display: flex;
  align-items: center;
  gap: clamp(1rem, 2.4vw, 2.2rem);
  font-size: var(--t-sm);
}
/* Micro type: small, uppercase and widely tracked, set in the monospace stack
   the brand already uses for its braces and code marks. It reads as a label
   rather than as body text, which is what keeps a capsule this size legible
   without the links competing with the logo. */
.nav > a,
.nav-trigger {
  position: relative;
  color: var(--mute);
  transition: color 0.18s var(--ease);
  font-family: var(--mono, ui-monospace, "SFMono-Regular", "Space Mono", Menlo, Consolas, monospace);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  padding-block: 0.55rem;
}
.nav > a:hover,
.nav > a[aria-current="page"],
.nav-trigger:hover,
.nav-trigger[aria-expanded="true"] { color: var(--bone); }

/* The current page is marked with a quiet pill rather than an underline. An
   underline inside a capsule sits a few pixels above the capsule's own bottom
   edge and the two read as a doubled line. */
.nav > a[aria-current="page"] {
  background: rgba(255, 255, 255, 0.07);
  border-radius: 999px;
  padding-inline: 0.9em;
}
.nav > a[aria-current="page"]::after { content: none; }

/* A hairline that grows from the centre on hover. Cheap, and it gives the
   nav a state that is visible without relying on colour alone. */
.nav > a::after,
.nav-trigger::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  bottom: 0;
  height: 1px;
  background: currentColor;
  transform: scaleX(0);
  transition: transform 0.25s var(--ease);
}
.nav > a:hover::after,
.nav > a[aria-current="page"]::after,
.nav-trigger:hover::after,
.nav-trigger[aria-expanded="true"]::after { transform: scaleX(1); }

/* --- Services mega menu -------------------------------------------------- */

.nav-item { position: relative; display: flex; align-items: center; }

/* Contact block belongs to the mobile overlay only */
.nav-foot { display: none; }

.nav-trigger {
  /* No font declarations here at all. This rule sits below the nav
     typography, so anything it sets wins: "font: inherit" was pulling the
     button back to the body face and size, which is why the links rendered at
     11px monospace and Services at 14.7px Chivo in the same menu. The
     typography rule above is the one that should own this. */
  background: none;
  border: 0;
  cursor: pointer;
  display: inline-flex;
  align-items: center;
  gap: 0.45em;
}
.nav-trigger i {
  width: 0.34em;
  height: 0.34em;
  border-right: 1.5px solid currentColor;
  border-bottom: 1.5px solid currentColor;
  transform: rotate(45deg) translate(-0.09em, -0.09em);
  transition: transform 0.25s var(--ease);
}
.nav-trigger[aria-expanded="true"] i { transform: rotate(225deg) translate(-0.02em, -0.02em); }

.mega {
  position: absolute;
  top: calc(100% + 1.15rem);
  left: 50%;
  /* Ten services now, so three columns rather than two. Wider than the
     trigger but still inside the capsule's own width at any real screen. */
  width: min(56rem, 92vw);
  display: grid;
  grid-template-columns: repeat(3, minmax(0, 1fr));
  gap: 0.2rem;
  padding: 0.7rem;
  border-radius: 1.25rem;
  z-index: 70;
  transform: translateX(-50%) translateY(-0.5rem);
  opacity: 0;
  pointer-events: none;
  transition: opacity 0.22s var(--ease), transform 0.22s var(--ease);
}
/* The panel sits a gap below the trigger. Without something filling that gap
   the pointer leaves the item on the way down and the panel closes under the
   cursor. */
.mega::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -1.3rem;
  height: 1.3rem;
}

.mega[hidden] { display: none; }
.nav-item[data-open="true"] .mega {
  opacity: 1;
  transform: translateX(-50%) translateY(0);
  pointer-events: auto;
}

@media (max-width: 1180px) {
  .mega { grid-template-columns: repeat(2, minmax(0, 1fr)); width: min(42rem, 92vw); }
}


/* Bridges the gap between trigger and panel so the pointer can travel
   between them without the menu closing underneath it */
.mega::before {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: -1.4rem;
  height: 1.4rem;
}

.mega a {
  display: block;
  padding: 0.85rem 1rem;
  border-radius: 0.9rem;
  transition: background-color 0.18s var(--ease);
}
.mega a:hover { background: rgba(255, 255, 255, 0.06); }
.mega b {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-sm);
  color: var(--bone);
  margin-bottom: 0.2rem;
}
.mega span { font-size: var(--t-xs); color: var(--faint); line-height: 1.5; }

/* The nav overlay is a child of the header, so the logo and the close button
   need to sit above it inside the header's own stacking context */
.hdr .logo,
.hdr-actions { position: relative; z-index: 60; }

.hdr-actions { display: flex; align-items: center; gap: 0.65rem; }

/* Outlined pill rather than a solid block, so the capsule reads as one object
   with a button inside it rather than two competing surfaces. */
.hdr-actions .btn--fill {
  background: none;
  color: var(--bone);
  border: 1px solid var(--bone);
  border-radius: 999px;
  font-family: var(--mono, ui-monospace, "SFMono-Regular", Menlo, Consolas, monospace);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  padding: 0.85em 1.5em;
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease);
}
.hdr-actions .btn--fill:hover,
.hdr-actions .btn--fill:focus-visible {
  background: var(--bone);
  color: var(--ink);
}

.icon-btn {
  width: 2.5rem;
  height: 2.5rem;
  display: grid;
  place-items: center;
  border-radius: 999px;
  border: 1px solid var(--line);
  color: var(--bone);
  transition: border-color 0.2s var(--ease), color 0.2s var(--ease);
}
.icon-btn:hover { border-color: var(--accent); color: var(--accent); }
.icon-btn svg { width: 1.05rem; height: 1.05rem; fill: currentColor; }

/* --- Mobile menu -------------------------------------------------------- */

.menu-toggle {
  display: none;
  width: 2.5rem;
  height: 2.5rem;
  padding: 0;
  place-items: center;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: none;
  color: var(--bone);
  cursor: pointer;
}
.menu-toggle span {
  display: block;
  width: 1.05rem;
  height: 1px;
  background: currentColor;
  position: relative;
  transition: background-color 0.2s var(--ease);
}
.menu-toggle span::before,
.menu-toggle span::after {
  content: "";
  position: absolute;
  left: 0;
  width: 100%;
  height: 1px;
  background: currentColor;
  transition: transform 0.25s var(--ease);
}
.menu-toggle span::before { top: -5px; }
.menu-toggle span::after  { top: 5px; }

body[data-menu="open"] .menu-toggle span { background: transparent; }
body[data-menu="open"] .menu-toggle span::before { transform: translateY(5px) rotate(45deg); }
body[data-menu="open"] .menu-toggle span::after  { transform: translateY(-5px) rotate(-45deg); }

@media (max-width: 900px) {
  .menu-toggle { display: grid; }

  .nav {
    position: fixed;
    inset: 0;
    flex-direction: column;
    align-items: stretch;
    justify-content: flex-start;
    gap: 0;
    padding: 5.75rem var(--gutter) 2.5rem;
    background-color: rgba(14, 14, 16, 0.86);
    -webkit-backdrop-filter: blur(28px) saturate(180%);
    backdrop-filter: blur(28px) saturate(180%);
    z-index: 50;
    transform: translateY(-100%);
    visibility: hidden;
    transition: transform 0.35s var(--ease), visibility 0s linear 0.35s;
    overflow-y: auto;
  }
  @supports not ((backdrop-filter: blur(1px)) or (-webkit-backdrop-filter: blur(1px))) {
    .nav { background-color: rgba(14, 14, 16, 0.98); }
  }

  /* Each row is a full-width tap target with a hairline under it, which is
     easier to hit than centred text and reads as a list rather than a pile */
  .nav > a,
  .nav-trigger {
    padding-block: 1rem;
    border-bottom: 1px solid var(--line);
    width: 100%;
  }
  .nav > a::after,
  .nav-trigger::after { display: none; }
  .nav-item { display: block; width: 100%; }
  .nav-trigger { justify-content: space-between; }

  /* The mega panel becomes an inline expandable list */
  .mega {
    position: static;
    width: auto;
    transform: none;
    opacity: 1;
    pointer-events: auto;
    grid-template-columns: 1fr;
    gap: 0;
    padding: 0.35rem 0 0.85rem;
    border: 0;
    border-bottom: 1px solid var(--line);
    border-radius: 0;
    box-shadow: none;
    background: none;
    -webkit-backdrop-filter: none;
    backdrop-filter: none;
  }
  .mega::before { display: none; }
  .mega a { padding: 0.6rem 0 0.6rem 1rem; border-left: 1px solid var(--line); border-radius: 0; }
  .mega a:hover { background: none; }
  .mega b { font-size: var(--t-base); }

  /* Contact block pinned under the list */
  .nav-foot {
    margin-top: auto;
    padding-top: 2rem;
    display: grid;
    gap: 0.5rem;
  }
  .nav-foot a { font-size: var(--t-sm); color: var(--mute); }
  .nav-foot .btn { justify-content: center; margin-bottom: 0.75rem; }

  .nav > a,
  .nav-trigger {
    font-family: var(--display);
    font-weight: 700;
    font-size: var(--t-md);
    color: var(--bone);
  }
  body[data-menu="open"] .nav {
    transform: none;
    visibility: visible;
    transition: transform 0.35s var(--ease), visibility 0s;
  }
  body[data-menu="open"] { overflow: hidden; }

  /* The header CTA duplicates the menu on small screens */
  .hdr-actions .btn { display: none; }
}

/* --- Left rail: rotated contact, scroll progress ------------------------ */

.rail {
  position: fixed;
  left: 0;
  top: 0;
  bottom: 0;
  width: var(--rail);
  z-index: 40;
  display: grid;
  place-items: center;
  pointer-events: none;
}
.rail a {
  writing-mode: vertical-rl;
  transform: rotate(180deg);
  font-size: var(--t-xs);
  letter-spacing: 0.14em;
  color: var(--faint);
  pointer-events: auto;
  transition: color 0.2s var(--ease);
}
.rail a:hover { color: var(--accent); }

/* The reading indicator is a die now, so the track and the fill that used to
   live here are gone. A .progress span rule would land on the die itself and
   absolutely position it out of its own box. */
.progress {
  position: fixed;
  right: 1.4rem;
  top: 50%;
  transform: translateY(-50%);
  z-index: 40;
  pointer-events: none;
}

@media (max-width: 900px) {
  .rail, .progress { display: none; }
}

/* --- Hero --------------------------------------------------------------- */

.hero {
  position: relative;
  min-height: 100svh;
  display: flex;
  align-items: center;
  padding-block: 6rem 2.5rem;
  isolation: isolate;
}

/* The empty band above the headline on a phone was not padding. The hero
   reserves a full screen and centres its content inside it, so whenever the
   content is shorter than the screen the leftover is split top and bottom and
   shows up as dead space above the headline. Sizing to the content removes it.

   The top padding still has to clear the fixed header, measured at 77px, so
   6.5rem leaves about 27px of breathing room under it rather than sliding the
   headline underneath. */
@media (max-width: 900px) {
  .hero {
    min-height: auto;
    align-items: start;
    padding-block: 6.5rem 2.25rem;
  }
}

/* Hero background layers, back to front:
   -5 fallback wash · -4 dice canvas · -3 cursor light · -2 scrim · -1 grain
   Content sits at auto, above all of them. */

#dice-canvas {
  position: absolute;
  inset: 0;
  z-index: -4;
  width: 100%;
  height: 100%;
}

/* Static composition shown when WebGL is off or motion is reduced */
.hero-fallback {
  position: absolute;
  inset: 0;
  z-index: -5;
  background:
    radial-gradient(52rem 34rem at 76% 28%, rgba(255,255,255,0.045), transparent 68%),
    radial-gradient(38rem 30rem at 12% 82%, rgba(255,255,255,0.025), transparent 70%);
}

/* The same light, at page scale. Fixed to the viewport so it follows the
   pointer everywhere rather than only inside the hero, and at a lower
   intensity than the hero's because it sits behind ordinary reading text. */
.page-light {
  position: fixed;
  inset: 0;
  z-index: -5;
  pointer-events: none;
  background: radial-gradient(
    34rem 34rem at var(--mx, 70%) var(--my, 22%),
    rgba(255, 255, 255, 0.055),
    rgba(255, 255, 255, 0.018) 45%,
    transparent 72%);
  transition: opacity 0.4s var(--ease);
}

/* No pointer, no point. */
@media (pointer: coarse) {
  .page-light { display: none; }
}

/* Cursor light. Sits under the scrim, so it lifts the dice on the right
   without ever washing out the headline on the left. The custom properties
   default to a fixed position, which is what touch devices get. */
.hero-light {
  position: absolute;
  inset: 0;
  z-index: -3;
  pointer-events: none;
  background: radial-gradient(
    28rem 28rem at var(--mx, 72%) var(--my, 38%),
    rgba(255, 255, 255, 0.11),
    rgba(255, 255, 255, 0.04) 42%,
    transparent 70%);
}

/* Grain. Pure black renders as dead space on a large screen; a fine noise
   gives it material. Desaturated, so the palette stays monochrome. */
.hero::after {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  opacity: 0.055;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.8' numOctaves='4' stitchTiles='stitch'/%3E%3CfeColorMatrix type='saturate' values='0'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)'/%3E%3C/svg%3E");
  background-size: 140px 140px;
}

/* Scrim: keeps headline contrast steady wherever the dice happen to drift.
   Wide screens shade left to right, because the copy sits in the left
   column. Narrow screens shade top to bottom, because it does not. */
.hero::before {
  content: "";
  position: absolute;
  inset: 0;
  z-index: -2;
  pointer-events: none;
  background: linear-gradient(100deg,
    var(--ink) 0%,
    rgba(14, 14, 16, 0.92) 34%,
    rgba(14, 14, 16, 0.55) 56%,
    transparent 78%);
}

@media (max-width: 900px) {
  .hero::before {
    background: linear-gradient(180deg,
      rgba(14, 14, 16, 0.97) 0%,
      rgba(14, 14, 16, 0.96) 55%,
      rgba(14, 14, 16, 0.72) 82%,
      rgba(14, 14, 16, 0.35) 100%);
  }
}

.hero-inner { position: relative; width: 100%; }

.hero h1 {
  font-size: var(--t-2xl);
  max-width: 18ch;
  margin-bottom: 1.1rem;
}
/* The emphasised phrase is drawn as outlined type rather than coloured.
   It borrows the marquee's treatment, so the page has one idea, not two. */
.hero h1 em {
  font-style: normal;
  color: var(--bone);
}
@supports (-webkit-text-stroke: 1px black) {
  .hero h1 em {
    color: transparent;
    -webkit-text-stroke: 1.5px var(--bone);
  }
}

/* --- Rotating service line ---------------------------------------------- */
/* The headline names one discipline at a time, because the agency does eight
   and a sentence that lists all eight is not a headline. Every word is in the
   markup: a crawler reads the full list, a screen reader gets one sentence,
   and the animation only decides which word is on screen. */

.h1-rotate { display: grid; gap: 0.08em; }

/* Wider than a normal hero headline, and deliberately so. .hero h1 caps at
   18ch, which the longest service name overruns by about a word. The rotating
   variant has to fit its widest member, not its average one. */
.hero h1.h1-rotate { max-width: 23ch; }
.h1-lead, .h1-tail { display: block; }

.rotator {
  display: grid;
  grid-template-areas: "word";
  justify-items: start;
  /* Without this every word is stretched to the height of the tallest, so a
     short word like SEO left a full empty line under it. Measured: all nine
     words reporting 85px on a phone because one of them wrapped to two. */
  align-items: start;
}

/* Base state shows the first word only, so a headline still reads as a whole
   sentence if the script never runs. .has-js takes over from there. */
.rotator-word {
  grid-area: word;
  font-style: normal;
  color: var(--bone);
  opacity: 0;
  white-space: nowrap;
}
.rotator-word:first-child { opacity: 1; }

/* Words after the first are generated content, so they are not indexed as
   part of the H1. The pseudo-element inherits the colour and the stroke. */
.rotator-word[data-word]::before { content: attr(data-word); }

@supports (-webkit-text-stroke: 1px black) {
  .rotator-word { color: transparent; -webkit-text-stroke: 1.5px var(--bone); }
}

.has-js .rotator-word {
  opacity: 0;
  transform: translateY(0.26em);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
}
.has-js .rotator-word.is-on { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .has-js .rotator-word { transition: none; transform: none; }
}

/* The longest service name is about 12 times the headline's font size wide,
   while the available column is roughly 0.92 of the viewport. Solving the two
   against the headline's clamp puts the crossover near 800px, so nowrap stops
   at 900 rather than at a phone breakpoint. Below this the words wrap, which
   costs a line and never overflows. */
@media (max-width: 900px) {
  .rotator-word { white-space: normal; }
  .hero h1.h1-rotate { max-width: none; }
}

.hero-sub {
  color: var(--mute);
  font-size: var(--t-md);
  max-width: 60ch;
  line-height: 1.55;
  margin-bottom: 1.85rem;
}

.hero-cta { display: flex; flex-wrap: wrap; gap: 0.85rem; margin-bottom: 2.1rem; }

/* --- The gauge: this page, measured ------------------------------------- */
/* Replaces the three claims that used to sit here. In a monochrome system
   there is no green to mean "passing", so each reading carries a meter bar
   showing the value against its budget instead. That is more informative
   than a colour anyway: it shows how much headroom is left, not just
   pass or fail. */

.gauge { border-radius: 1.1rem; overflow: hidden; max-width: 54rem; }

.gauge-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  flex-wrap: wrap;
  padding: 0.85rem 1.15rem;
  border-bottom: 1px solid var(--line);
}
.gauge-head .brace { margin-bottom: 0; font-size: var(--t-xs); }
.gauge-when {
  font-family: var(--display);
  font-size: var(--t-xs);
  color: var(--faint);
  font-variant-numeric: tabular-nums;
}

.gauge-grid { display: grid; grid-template-columns: repeat(4, 1fr); }

.gauge-cell {
  padding: 1.05rem 1.15rem 1rem;
  border-right: 1px solid var(--line);
}
.gauge-cell:last-child { border-right: 0; }

.g-val {
  display: block;
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(1.5rem, 1rem + 1.5vw, 2.1rem);
  line-height: 1;
  letter-spacing: -0.035em;
  color: var(--bone);
  font-variant-numeric: tabular-nums;
  margin-bottom: 0.55rem;
}
.g-lbl { display: block; font-size: var(--t-xs); color: var(--mute); line-height: 1.35; }

/* Meter: filled portion is the reading, the track is the budget */
.g-meter {
  display: block;
  height: 2px;
  background: var(--line);
  margin: 0.85rem 0 0.5rem;
  overflow: hidden;
}
.g-meter i {
  display: block;
  height: 100%;
  width: 0;
  background: var(--bone);
  transition: width 1s var(--ease);
}
/* Over budget: the bar fills completely and turns into a hatch, so it is
   distinguishable without relying on colour or on seeing both states */
.gauge-cell[data-over="true"] .g-meter i {
  background: repeating-linear-gradient(
    -45deg, var(--mute) 0 3px, transparent 3px 6px);
}
.gauge-cell[data-over="true"] .g-val { color: var(--mute); }

.g-bud { display: block; font-size: var(--t-xs); color: var(--faint); font-variant-numeric: tabular-nums; }

.gauge-foot {
  margin: 0;
  padding: 0.85rem 1.15rem;
  border-top: 1px solid var(--line);
  font-size: var(--t-xs);
  color: var(--faint);
  max-width: none;
}

/* Two by two below tablet rather than a single tall column */
@media (max-width: 820px) {
  .gauge-grid { grid-template-columns: repeat(2, 1fr); }
  .gauge-cell:nth-child(2) { border-right: 0; }
  .gauge-cell:nth-child(-n+2) { border-bottom: 1px solid var(--line); }
}

@media (prefers-reduced-motion: reduce) {
  .g-meter i { transition: none; }
}

/* --- Service grid: staggered, not a uniform 3-up ------------------------ */

.svc-grid {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 1.8vw, 1.6rem);
  align-items: start;
}
/* The offset is the point: columns 2 and 3 drop, breaking the grid line */
.svc-grid > :nth-child(3n+2) { margin-top: clamp(1.5rem, 4vw, 4rem); }
.svc-grid > :nth-child(3n+3) { margin-top: clamp(3rem, 8vw, 8rem); }

.svc {
  display: block;
  border-radius: 1.25rem;
  padding: clamp(1.5rem, 2.4vw, 2.1rem);
  transition: border-color 0.25s var(--ease), background-color 0.25s var(--ease);
}
.svc:hover { border-color: rgba(255, 255, 255, 0.22); }

.svc-glyph {
  font-family: var(--display);
  font-size: 1.6rem;
  font-weight: 700;
  color: var(--faint);
  line-height: 1;
  margin-bottom: 2.4rem;
  display: block;
}

.svc h3 { font-size: var(--t-md); margin-bottom: 0.7rem; }
.svc p  { color: var(--mute); font-size: var(--t-sm); margin: 0 0 1.2rem; line-height: 1.6; }

.svc-more {
  font-family: var(--display);
  font-size: var(--t-xs);
  font-weight: 700;
  color: var(--faint);
  transition: color 0.2s var(--ease);
}
.svc:hover .svc-more { color: var(--bone); }

@media (max-width: 900px) {
  .svc-grid { grid-template-columns: repeat(2, 1fr); }
  .svc-grid > :nth-child(3n+2),
  .svc-grid > :nth-child(3n+3) { margin-top: 0; }
  .svc-grid > :nth-child(even) { margin-top: 2rem; }
}
@media (max-width: 600px) {
  .svc-grid { grid-template-columns: 1fr; }
  .svc-grid > * { margin-top: 0 !important; }
}

/* --- Marquee band ------------------------------------------------------- */

.marquee {
  overflow: hidden;
  border-block: 1px solid var(--line);
  padding-block: clamp(1rem, 2vw, 2rem);
  user-select: none;
}
.marquee-track {
  display: flex;
  align-items: center;
  gap: 0.28em;
  width: max-content;
  animation: slide 42s linear infinite;
}
/* A slash separates the words, so the band reads as a list rather than
   one run-on string. Set a step quieter than the words themselves. */
.marquee-track b {
  flex: none;
  font-family: var(--display);
  font-weight: 500;
  font-size: var(--t-huge);
  line-height: 0.95;
  color: transparent;
  -webkit-text-stroke: 1.5px var(--faint);
  opacity: 0.5;
}
.marquee-track b::before { content: "/"; }
.marquee span {
  font-family: var(--display);
  font-size: var(--t-huge);
  font-weight: 700;
  line-height: 0.95;
  letter-spacing: -0.045em;
  color: transparent;
  -webkit-text-stroke: 1.5px var(--mute);
  white-space: nowrap;
}
/* Alternate words fill instead of outlining, so the band has a rhythm
   rather than reading as one long outlined string. The fill is a dark
   grey, not white, so it stays a background element. */
.marquee span:nth-of-type(even) {
  color: var(--panel-2);
  -webkit-text-stroke-width: 0;
}
@keyframes slide { to { transform: translateX(-50%); } }

/* --- Work / case study cards -------------------------------------------- */

.work-grid {
  display: grid;
  grid-template-columns: repeat(2, 1fr);
  gap: clamp(1rem, 2vw, 2rem);
}
.work {
  position: relative;
  display: block;
  border-radius: 1.25rem;
  overflow: hidden;
}
.work:hover { border-color: rgba(255, 255, 255, 0.22); }

.work-shot {
  aspect-ratio: 16 / 10;
  background: linear-gradient(148deg, var(--panel-2), var(--ink-raise));
  display: grid;
  place-items: center;
  position: relative;
  overflow: hidden;
}
.work-shot::after {
  content: attr(data-note);
  font-family: var(--display);
  font-size: var(--t-xs);
  color: var(--faint);
  letter-spacing: 0.06em;
}
.work-body { padding: clamp(1.3rem, 2.2vw, 1.9rem); }
.work-meta {
  display: flex;
  gap: 0.5rem;
  font-size: var(--t-xs);
  color: var(--faint);
  margin-bottom: 0.7rem;
  font-family: var(--display);
}
.work h3 { font-size: var(--t-md); margin-bottom: 0.9rem; }
.work-result {
  font-family: var(--display);
  font-weight: 700;
  color: var(--bone);
  font-size: var(--t-sm);
}

@media (max-width: 700px) { .work-grid { grid-template-columns: 1fr; } }

/* --- Process: dice faces as step markers -------------------------------- */
/* Six steps, six faces of a die. The sequence is real, so the marker is    */
/* allowed to be a counter — and it is the brand's own object.             */

/* An <ol>, because six numbered stages genuinely are a sequence. The list
   markers are suppressed since the dice faces carry the numbering visually. */
.process {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1.75rem, 3.5vw, 3.25rem);
  list-style: none;
  margin: 0;
  padding: 0;
}
.step { position: relative; padding-top: 1.75rem; border-top: 1px solid var(--line); }
.step h3 { font-size: var(--t-md); margin: 1.15rem 0 0.5rem; }
.step p  { color: var(--mute); font-size: var(--t-sm); margin: 0; }

.die {
  width: 2.9rem;
  height: 2.9rem;
  border-radius: 0.6rem;
  display: grid;
  grid-template: repeat(3, 1fr) / repeat(3, 1fr);
  gap: 0.22rem;
  padding: 0.5rem;
}
.die i {
  width: 0.32rem;
  height: 0.32rem;
  border-radius: 50%;
  background: var(--mute);
  align-self: center;
  justify-self: center;
}
/* Pip placement per face */
.die[data-face="1"] i:nth-child(1) { grid-area: 2 / 2; }
.die[data-face="2"] i:nth-child(1) { grid-area: 1 / 1; }
.die[data-face="2"] i:nth-child(2) { grid-area: 3 / 3; }
.die[data-face="3"] i:nth-child(1) { grid-area: 1 / 1; }
.die[data-face="3"] i:nth-child(2) { grid-area: 2 / 2; }
.die[data-face="3"] i:nth-child(3) { grid-area: 3 / 3; }
.die[data-face="4"] i:nth-child(1) { grid-area: 1 / 1; }
.die[data-face="4"] i:nth-child(2) { grid-area: 1 / 3; }
.die[data-face="4"] i:nth-child(3) { grid-area: 3 / 1; }
.die[data-face="4"] i:nth-child(4) { grid-area: 3 / 3; }
.die[data-face="5"] i:nth-child(1) { grid-area: 1 / 1; }
.die[data-face="5"] i:nth-child(2) { grid-area: 1 / 3; }
.die[data-face="5"] i:nth-child(3) { grid-area: 2 / 2; }
.die[data-face="5"] i:nth-child(4) { grid-area: 3 / 1; }
.die[data-face="5"] i:nth-child(5) { grid-area: 3 / 3; }
.die[data-face="6"] i:nth-child(1) { grid-area: 1 / 1; }
.die[data-face="6"] i:nth-child(2) { grid-area: 1 / 3; }
.die[data-face="6"] i:nth-child(3) { grid-area: 2 / 1; }
.die[data-face="6"] i:nth-child(4) { grid-area: 2 / 3; }
.die[data-face="6"] i:nth-child(5) { grid-area: 3 / 1; }
.die[data-face="6"] i:nth-child(6) { grid-area: 3 / 3; }

/* Every die now carries six pips and the face hides the ones it does not
   need. It costs five empty spans and it buys a die whose face can change
   without the markup being rebuilt, which is what the reading indicator and
   the 404 both need. */
.die[data-face="1"] i:nth-child(n+2),
.die[data-face="2"] i:nth-child(n+3),
.die[data-face="3"] i:nth-child(n+4),
.die[data-face="4"] i:nth-child(n+5),
.die[data-face="5"] i:nth-child(n+6) { display: none; }

/* A pip that appears should fade rather than blink into place. */
.die i { transition: opacity 0.3s var(--ease), background-color 0.3s var(--ease); }

/* Roll in -----------------------------------------------------------------
   The die tips onto its face as its section arrives, one after another. It
   rides the section's own reveal rather than adding an observer, and the
   stagger comes from --i on the step, so six of them land over about half a
   second.

   An animation rather than a transition, and this matters. A staggered
   transition has to carry its delay on the state it settles into, which means
   the same delay then applies to every later change of transform on that
   element: the last die in a row would sit still for a third of a second
   before acknowledging a hover. An animation owns its own delay and leaves
   the transition free.

   Fill mode is backwards, not both. Backwards holds the start frame through
   the delay and then lets go at the end, so the finished die is styled by the
   ordinary cascade again. With both, the animation would keep asserting
   transform:none for the life of the page and the hover tilt would never be
   allowed to apply.

   Transform and opacity only. Both composite, so none of this touches
   layout. */
@media (prefers-reduced-motion: no-preference) {
  .has-js [data-reveal] .die--roll { opacity: 0; }

  .has-js [data-reveal].is-in .die--roll {
    opacity: 1;
    animation: dc-die-in 0.5s cubic-bezier(0.2, 1.5, 0.4, 1) backwards;
    animation-delay: calc(var(--i, 0) * 65ms);
  }
}

@keyframes dc-die-in {
  from { opacity: 0; transform: rotate(-38deg) scale(0.72); }
  to   { opacity: 1; transform: none; }
}

/* Tilt on hover. The whole card is the target, because the die is small and
   asking somebody to hit a 46 pixel square to see an effect is a poor trade. */
.die { transition: transform 0.3s var(--ease), border-color 0.3s var(--ease); }

.step:hover .die,
.svc:hover .die { border-color: var(--glass-line-lit); }

.step:hover .die { transform: rotate(10deg) scale(1.08); }
.svc:hover .die { transform: rotate(-10deg) scale(1.08); }

.step:hover .die i,
.svc:hover .die i { background: var(--bone); }

/* In a service grid ------------------------------------------------------- */

.svc-die { margin-bottom: 1rem; }

/* The reading indicator --------------------------------------------------- */

.progress .die {
  width: 2.1rem;
  height: 2.1rem;
  border-radius: 0.45rem;
  padding: 0.36rem;
  gap: 0.15rem;
}
.progress .die i { width: 0.24rem; height: 0.24rem; }

/* The 404 and the empty search ------------------------------------------- */

.die--lg {
  width: 4.6rem;
  height: 4.6rem;
  border-radius: 0.95rem;
  padding: 0.8rem;
  gap: 0.34rem;
}
.die--lg i { width: 0.52rem; height: 0.52rem; background: var(--bone); }

.die-roll {
  display: inline-block;
  padding: 0;
  margin: 0 0 1.5rem;
  background: none;
  border: 0;
  cursor: pointer;
  line-height: 0;
}
.die-roll:hover .die { transform: rotate(-8deg) scale(1.05); border-color: var(--glass-line-lit); }
.die-roll:focus-visible { outline: 2px solid var(--bone); outline-offset: 6px; border-radius: 1rem; }

/* One tumble. It turns twice and shrinks through the middle of the turn, so
   the die reads as leaving the surface rather than as a flat spin. The face
   is swapped at the halfway point, where it is edge on. */
.die--lg.is-rolling { animation: dc-tumble 0.52s var(--ease); }

@keyframes dc-tumble {
  0%   { transform: rotate(0) scale(1); }
  45%  { transform: rotate(340deg) scale(0.72); }
  100% { transform: rotate(720deg) scale(1); }
}

.empty {
  display: grid;
  justify-items: start;
  gap: 1.25rem;
  padding-block: 1rem 2rem;
}
.empty p { color: var(--mute); margin: 0; }

@media (max-width: 880px) { .process { grid-template-columns: repeat(2, 1fr); } }
@media (max-width: 560px) { .process { grid-template-columns: 1fr; } }

/* --- Statement block ---------------------------------------------------- */

/* Only the lead sentence gets the display treatment. The paragraphs that
   follow stay at reading size, or the whole block becomes a wall. */
.statement .lead {
  font-family: var(--display);
  font-weight: 500;
  font-size: var(--t-md);
  line-height: 1.36;
  letter-spacing: -0.015em;
  color: var(--bone);
  max-width: 40ch;
  margin-bottom: 1.5rem;
}
.statement p { color: var(--mute); max-width: 56ch; }
.statement .btn { margin-top: 0.75rem; }

/* --- Testimonials ------------------------------------------------------- */

.quotes {
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(1rem, 2vw, 1.75rem);
}
.quote {
  border-radius: 1.25rem;
  padding: clamp(1.5rem, 2.4vw, 2.1rem);
}
.quote--wide { max-width: 44rem; }
.quote blockquote { margin: 0 0 1.5rem; font-size: var(--t-sm); line-height: 1.7; color: var(--bone); }
.quote figcaption { font-size: var(--t-xs); color: var(--faint); }
.quote figcaption b { display: block; color: var(--bone); font-family: var(--display); font-size: var(--t-sm); margin-bottom: 0.15rem; }
.stars { color: var(--mute); font-size: var(--t-sm); letter-spacing: 0.15em; margin-bottom: 1rem; }

/* Three columns suit three quotes. Four left one stranded on a row of its
   own, and a single quote in a case study sat in a third of the width with
   two empty columns beside it. */
.quotes:has(> :nth-child(4):last-child) { grid-template-columns: repeat(2, 1fr); }
.quotes:has(> :only-child) { grid-template-columns: 1fr; max-width: 46rem; }

@media (max-width: 880px) { .quotes { grid-template-columns: 1fr; } }

/* --- FAQ ---------------------------------------------------------------- */

.faq { border-top: 1px solid var(--line); }
.faq details { border-bottom: 1px solid var(--line); }
.faq summary {
  cursor: pointer;
  list-style: none;
  padding: 1.5rem 3rem 1.5rem 0;
  font-family: var(--display);
  font-weight: 700;
  font-size: var(--t-md);
  position: relative;
  transition: color 0.2s var(--ease);
}
.faq summary::-webkit-details-marker { display: none; }
.faq summary:hover { color: var(--accent); }
.faq summary::after {
  content: "+";
  position: absolute;
  right: 0;
  top: 50%;
  transform: translateY(-50%);
  color: var(--faint);
  font-size: 1.5rem;
  font-weight: 400;
}
.faq details[open] summary::after { content: "\2013"; }
.faq details p { color: var(--mute); font-size: var(--t-sm); padding-bottom: 1.6rem; margin: 0; }

/* --- CTA band ----------------------------------------------------------- */

.cta-band {
  background: var(--accent);
  color: var(--ink);
  border-radius: 1.75rem;
  padding: clamp(2.5rem, 6vw, 5rem);
  display: grid;
  grid-template-columns: minmax(0, 1.2fr) minmax(0, 0.8fr);
  gap: clamp(1.5rem, 4vw, 4rem);
  align-items: center;
}
.cta-band h2 { font-size: var(--t-xl); color: var(--ink); max-width: 15ch; }
.cta-band p  { color: rgba(14, 14, 16, 0.72); margin: 1rem 0 0; font-size: var(--t-sm); }
.cta-band .btn--fill { background: var(--ink); color: var(--accent); }
.cta-band .btn--fill:hover { background: #000; }
.cta-actions { display: flex; flex-direction: column; gap: 0.75rem; align-items: start; }
.cta-actions a { width: fit-content; }

@media (max-width: 780px) { .cta-band { grid-template-columns: 1fr; } }

/* --- Footer ------------------------------------------------------------- */

/* The footer has to read as a different surface, not as the page running out.
   Last time I cut its top padding, which only made the last block sit closer
   to a hairline nobody could see. The separation comes from the surface
   change instead: the footer is a shade lighter than the page, with the
   content given room above it rather than the footer given padding. */
.ftr {
  border-top: 1px solid var(--line);
  background: var(--ink-raise);
  padding-block: clamp(3.5rem, 6vw, 5.5rem) 2.5rem;
  position: relative;
}

/* Room between the last thing on the page and the footer edge, applied to the
   content rather than to the footer, so the surface change lands cleanly. */
#main { padding-bottom: clamp(3.5rem, 7vw, 6rem); }
.ftr-grid {
  display: grid;
  grid-template-columns: 1.4fr repeat(3, 1fr);
  gap: clamp(2rem, 4vw, 3.5rem);
  margin-bottom: 4rem;
}
.ftr .foot-title {
  font-size: var(--t-xs);
  color: var(--faint);
  font-weight: 500;
  letter-spacing: 0.02em;
  margin-bottom: 1.2rem;
}
.ftr ul { list-style: none; margin: 0; padding: 0; display: grid; gap: 0.65rem; }
.ftr li a { font-size: var(--t-sm); color: var(--mute); transition: color 0.18s var(--ease); }
.ftr li a:hover { color: var(--accent); }
.ftr-blurb { color: var(--mute); font-size: var(--t-sm); max-width: 34ch; margin: 1.2rem 0 0; }

.ftr-base {
  border-top: 1px solid var(--line);
  padding-top: 2rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: space-between;
  gap: 1rem;
  font-size: var(--t-xs);
  color: var(--faint);
}
.ftr-base a { color: var(--faint); }
.ftr-base a:hover { color: var(--accent); }

@media (max-width: 880px) { .ftr-grid { grid-template-columns: 1fr 1fr; } }

/* --- Floating contact --------------------------------------------------- */

.float {
  position: fixed;
  right: 1.4rem;
  bottom: 1.4rem;
  z-index: 55;
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.float a {
  width: 3rem;
  height: 3rem;
  border-radius: 999px;
  display: grid;
  place-items: center;
  color: var(--bone);
  transition: background-color 0.2s var(--ease), color 0.2s var(--ease);
}
.float a:hover { background-color: var(--accent); color: var(--ink); border-color: var(--accent); }
.float svg { width: 1.15rem; height: 1.15rem; fill: currentColor; }

/* --- Breadcrumbs -------------------------------------------------------- */

.crumbs {
  font-size: var(--t-xs);
  color: var(--faint);
  padding-top: 7.5rem;
}

/* The theme now builds its own trail as an ordered list rather than printing
   a plugin's markup, so the separator is drawn rather than typed. A typed
   slash between links is read aloud by a screen reader as a word. */
.crumbs ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
}

.crumbs li { display: flex; align-items: center; gap: 0.5rem; }

.crumbs li + li::before {
  content: "";
  width: 0.3rem;
  height: 0.3rem;
  border-top: 1px solid var(--line);
  border-right: 1px solid var(--line);
  transform: rotate(45deg);
}

.crumbs a {
  color: var(--faint);
  text-decoration: none;
  /* 44px is the smallest target a thumb hits reliably. The link text is only
     21px tall, so the rest comes from padding that does not disturb the
     layout. */
  padding-block: 0.8rem;
  margin-block: -0.8rem;
}
.crumbs a:hover,
.crumbs a:focus-visible { color: var(--bone); }
.crumbs span[aria-current] { color: var(--mute); }

@media (max-width: 700px) {
  /* On a phone the middle of a long trail is noise. Keep the way back and
     the page you are on. */
  .crumbs li:not(:first-child):not(:last-child) { display: none; }
  .crumbs li:last-child::before { content: none; }
  .crumbs li:first-child::after {
    content: "";
    width: 0.3rem;
    height: 0.3rem;
    border-top: 1px solid var(--line);
    border-right: 1px solid var(--line);
    transform: rotate(45deg);
  }
}

/* --- Inner page hero ---------------------------------------------------- */

/* A full step under an inner page headline pushed the first real content
   off the opening screen. */
.page-hero { padding-block: 3rem clamp(2.5rem, 5vw, 4.5rem); }
.page-hero h1 { font-size: var(--t-xl); max-width: 18ch; margin-bottom: 1.5rem; }

/* --- Post header ---------------------------------------------------------
 * A post title is the page, so it gets the hero size rather than the inner
 * page size. The 18ch cap that suits a short service headline was forcing an
 * eight word article title into four narrow lines with the right half of the
 * screen empty beside it, which read as a gap rather than as restraint.
 *
 * The vertical padding is tightened for the same reason: the section step is
 * up to 152px, which is right between two sections and far too much between
 * a title and the article it introduces.
 * ---------------------------------------------------------------------- */

.post-head { padding-block: 1.25rem 3rem; }

.post-head h1 {
  /* Wider rather than larger. At the hero size a 74 character title still
     needed four lines and grew the header instead of filling it, so the size
     comes back down and the width goes up: same words, fewer lines, less
     vertical space spent. */
  font-size: var(--t-xl);
  max-width: 30ch;
  margin-bottom: 1.5rem;
  text-wrap: balance;
}

.post-head .brace { margin-bottom: 1.25rem; }

.post-head .post-meta { color: var(--faint); }

@media (max-width: 700px) {
  .post-head { padding-block: 1rem 2rem; }
  .post-head h1 { max-width: none; }
}
.page-hero .hero-sub { max-width: 56ch; }
.page-hero .hero-cta { margin-top: 2.5rem; margin-bottom: 0; }

/* A brace label placed below the block it annotates rather than above */
.brace--note { margin-top: 1.5rem; margin-bottom: 0; }

/* Answer-first paragraph. Leads with the direct answer so AI engines and
   featured snippets can lift it cleanly. */
.answer {
  border-left: 2px solid var(--line);
  padding-left: clamp(1rem, 2vw, 1.75rem);
  font-size: var(--t-md);
  line-height: 1.55;
  color: var(--bone);
  max-width: 60ch;
}
.answer p:last-child { margin-bottom: 0; }

/* --- Two column prose --------------------------------------------------- */

.split {
  display: grid;
  grid-template-columns: minmax(0, 0.85fr) minmax(0, 1.15fr);
  gap: clamp(1.75rem, 5vw, 5rem);
  align-items: start;
}
.split h2 { font-size: var(--t-lg); position: sticky; top: 6.5rem; }
.split > div > p { color: var(--mute); }
.split > div > p strong { color: var(--bone); font-weight: 700; }
@media (max-width: 820px) {
  .split { grid-template-columns: 1fr; }
  .split h2 { position: static; }
}

/* --- Feature list ------------------------------------------------------- */

/* No 01/02/03 markers here. These are components of a service, not a
   sequence, and the dice pips on the process section are the counter this
   brand already owns. A brace marks each item instead. */
.features { display: grid; gap: clamp(0.7rem, 1.4vw, 1.1rem); }

.feature {
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  box-shadow: var(--glass-lip);
  padding: 1.55rem 1.75rem 1.55rem 3rem;
  position: relative;
  transition: transform 0.3s var(--ease), border-color 0.3s var(--ease);
}

.feature:hover {
  transform: translateY(-3px);
  border-color: var(--glass-line-lit);
}

/* As hairline rows these read correctly at full width. As cards a 1200px
   wide box holding two lines of text does not, so they pair up once there is
   room for two. */
@media (min-width: 860px) {
  .features { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.feature h3 { font-size: var(--t-md); margin-bottom: 0.5rem; }
.feature h3::before {
  content: "{";
  position: absolute;
  left: 1.7rem;
  color: var(--faint);
  font-weight: 500;
}
.feature p { color: var(--mute); font-size: var(--t-sm); margin: 0; }

/* --- Pill link grid (niche landing pages) ------------------------------- */

.pills { display: flex; flex-wrap: wrap; gap: 0.6rem; }
.pill {
  border-radius: 999px;
  padding: 0.6em 1.15em;
  font-size: var(--t-sm);
  color: var(--mute);
  transition: color 0.2s var(--ease);
  /* A pill came out at 43px, a whisker under the 44px a thumb needs. */
  display: inline-flex;
  align-items: center;
  min-height: 44px;
}
.pill:hover { color: var(--bone); }

/* --- Stat row ----------------------------------------------------------- */

.stats {
  display: grid;
  grid-template-columns: repeat(4, 1fr);
  gap: clamp(0.7rem, 1.4vw, 1.1rem);
  padding-block: 0;
}

.stat {
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  box-shadow: var(--glass-lip);
  padding: clamp(1.2rem, 2.4vw, 1.75rem);
  transition: transform 0.3s var(--ease), border-color 0.3s var(--ease);
}

.stat:hover {
  transform: translateY(-3px);
  border-color: var(--glass-line-lit);
}
.stat strong {
  display: block;
  font-family: var(--display);
  font-size: var(--t-lg);
  font-weight: 700;
  letter-spacing: -0.035em;
  color: var(--accent);
  line-height: 1.05;
  margin-bottom: 0.6rem;
}
.stat span { font-size: var(--t-sm); color: var(--mute); }
@media (max-width: 700px) { .stats { grid-template-columns: repeat(2, 1fr); } }

/* --- Table -------------------------------------------------------------- */

/* The table is a panel, not a grid of rules. The old version drew a line
   under every cell and a box around the lot, which is how a spreadsheet looks
   and not how the rest of this site looks.

   It must also scroll. The table has a 34rem floor, the body clips horizontal
   overflow, and the two together silently cut the right-hand columns off on a
   phone: the reader could not reach them and nothing told them so. */

/* The figure carries the page gutter, so it must not also carry the frame:
   putting the border here left the table inset by a full gutter inside its own
   panel. The frame belongs on the table. */
.wp-block-table {
  overflow-x: auto;
  -webkit-overflow-scrolling: touch;
  margin: 0;
}

.wp-block-table table {
  border: 1px solid var(--glass-line);
  border-radius: 14px;
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border-collapse: separate;
  border-spacing: 0;
  overflow: hidden;
}

.table-scroll { overflow-x: auto; }

table {
  width: 100%;
  border-collapse: collapse;
  font-size: var(--t-sm);
  min-width: 34rem;
}

thead tr:first-child th:first-child { border-top-left-radius: 13px; }
thead tr:first-child th:last-child  { border-top-right-radius: 13px; }

thead th {
  font-family: var(--display);
  font-size: 0.78rem;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--faint);
  font-weight: 700;
  background: var(--panel);
  padding: 0.95rem 1.25rem;
  border-bottom: 1px solid var(--line);
  white-space: nowrap;
}

tbody td {
  color: var(--mute);
  padding: 1rem 1.25rem;
  border-bottom: 1px solid rgba(255, 255, 255, 0.045);
  vertical-align: top;
}

tbody tr:last-child td { border-bottom: 0; }
tbody tr:hover td { background: rgba(255, 255, 255, 0.022); }

th, td { text-align: left; }
td strong { color: var(--bone); font-weight: 700; }

/* --- Comparison table --------------------------------------------------- */
/* Two columns making an argument. Rendering both identically throws the
   argument away: the first column is the question, the second is the answer
   being sold, and the third is what the reader has today. */

.is-compare tbody td:first-child {
  font-family: var(--display);
  color: var(--bone);
  font-weight: 700;
  width: 28%;
  background: rgba(255, 255, 255, 0.016);
}

.is-compare thead th:nth-child(2),
.is-compare tbody td:nth-child(2) {
  background: rgba(255, 255, 255, 0.05);
  color: var(--bone);
  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.08),
              inset -1px 0 0 rgba(255, 255, 255, 0.08);
}

.is-compare thead th:nth-child(2) { color: var(--bone); }
.is-compare tbody tr:hover td:nth-child(2) { background: rgba(255, 255, 255, 0.07); }

/* The caption sits outside the panel, so it needs its own air. */
.wp-block-table + .wp-element-caption,
.wp-block-table .wp-element-caption {
  margin-top: 0.85rem;
  color: var(--faint);
  font-size: var(--t-sm);
}

/* A label and value table has no columns worth preserving on a phone. The
   34rem floor was pushing the contact details into a sideways scroll inside a
   375px screen, so the phone number sat off the edge. Stacked, it fits. */
@media (max-width: 640px) {
  .wp-block-table:not(.is-compare) table { min-width: 0; }

  .wp-block-table:not(.is-compare) tr {
    display: grid;
    gap: 0.2rem;
    padding: 0.95rem 1.1rem;
    border-bottom: 1px solid rgba(255, 255, 255, 0.05);
  }

  .wp-block-table:not(.is-compare) tr:last-child { border-bottom: 0; }

  .wp-block-table:not(.is-compare) td {
    border: 0;
    padding: 0;
  }

  .wp-block-table:not(.is-compare) td:first-child {
    font-family: var(--display);
    font-weight: 700;
    color: var(--bone);
    font-size: 0.78rem;
    letter-spacing: 0.04em;
    text-transform: uppercase;
  }

  .wp-block-table:not(.is-compare) td:last-child {
    color: var(--faint);
    font-size: 0.82rem;
  }
}

@media (max-width: 700px) {
  thead th, tbody td { padding: 0.8rem 0.9rem; }
  .is-compare tbody td:first-child { width: auto; }
}

/* --- Comparison ---------------------------------------------------------- */
/* A two-way comparison is an argument. The recommended side runs as one
   continuous raised panel down the whole block so the answer is legible
   before a word is read, which is the thing a table cannot do: a table gives
   both columns identical weight and leaves the reader to work it out. */

.versus { margin: 0; }

.versus-grid {
  display: grid;
  grid-template-columns: minmax(0, 0.9fr) minmax(0, 1.2fr) minmax(0, 1.2fr);
  column-gap: 0;
  align-items: stretch;
}

/* Column headings */

.versus-corner { display: block; }

.versus-col {
  padding: 1.15rem 1.5rem;
  border-bottom: 1px solid var(--line);
}
.versus-col b {
  font-family: var(--display);
  font-size: var(--t-md);
  color: var(--mute);
  font-weight: 700;
  display: block;
}

.versus-col--ours {
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border-radius: 16px 16px 0 0;
  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.09),
              inset -1px 0 0 rgba(255, 255, 255, 0.09),
              inset 0 1px 0 rgba(255, 255, 255, 0.09);
}
.versus-col--ours b { color: var(--bone); }

.versus-tag {
  display: inline-block;
  font-family: var(--display);
  font-size: 0.66rem;
  letter-spacing: 0.11em;
  text-transform: uppercase;
  color: var(--ink);
  background: var(--bone);
  border-radius: 999px;
  padding: 0.22em 0.7em;
  margin-bottom: 0.6rem;
}

/* Rows */

.versus-label {
  font-family: var(--display);
  font-weight: 700;
  color: var(--bone);
  font-size: var(--t-sm);
  padding: 1.15rem 1.5rem 1.15rem 0;
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.versus-cell {
  padding: 1.15rem 1.5rem;
  font-size: var(--t-sm);
  color: var(--mute);
  border-bottom: 1px solid rgba(255, 255, 255, 0.05);
}

.versus-cell--ours {
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  color: var(--bone);
  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.09),
              inset -1px 0 0 rgba(255, 255, 255, 0.09);
}

/* A quiet mark on the recommended side. Monochrome, so it reads as emphasis
   rather than as a tick chasing approval. */
.versus-cell--ours::before {
  content: "";
  float: left;
  width: 0.42rem;
  height: 0.42rem;
  margin: 0.5em 0.6rem 0 0;
  background: var(--bone);
  border-radius: 1px;
}

.versus-grid > .versus-cell:nth-last-child(2) { border-bottom: 0; }
.versus-grid > .versus-cell:last-child { border-bottom: 0; }
.versus-grid > .versus-label:nth-last-child(3) { border-bottom: 0; }

.versus-cell--ours:nth-last-child(2) {
  border-radius: 0 0 16px 16px;
  box-shadow: inset 1px 0 0 rgba(255, 255, 255, 0.09),
              inset -1px 0 0 rgba(255, 255, 255, 0.09),
              inset 0 -1px 0 rgba(255, 255, 255, 0.09);
}

.versus-note {
  color: var(--faint);
  font-size: var(--t-sm);
  margin: 1rem 0 0;
}

/* Stacked on a phone: label, then the two answers as a labelled pair. A
   three-column grid at this width would need sideways scrolling, which is
   what the old table did. */
@media (max-width: 820px) {
  .versus-grid { grid-template-columns: 1fr; }

  .versus-corner,
  .versus-col { display: none; }

  .versus-label {
    padding: 1.6rem 0 0.7rem;
    border-bottom: 0;
    font-size: var(--t-md);
  }

  .versus-cell {
    border-bottom: 0;
    border-radius: 12px;
    padding: 0.9rem 1.1rem;
    margin-bottom: 0.5rem;
  }

  .versus-cell--ours {
    border-radius: 12px;
    box-shadow: inset 0 0 0 1px rgba(255, 255, 255, 0.09);
  }

  .versus-cell--theirs {
    color: var(--faint);
    box-shadow: inset 0 0 0 1px var(--line);
  }

  /* The column headings are hidden at this width, so each card has to say
     which side it belongs to or a reader scrolling in has no idea. */
  .versus-cell::before {
    content: attr(data-side);
    float: none;
    display: block;
    width: auto;
    height: auto;
    margin: 0 0 0.35rem;
    background: none;
    font-family: var(--display);
    font-size: 0.68rem;
    letter-spacing: 0.09em;
    text-transform: uppercase;
    color: var(--faint);
  }

  .versus-cell--ours::before { color: var(--bone); }
}

/* --- Mobile action bar --------------------------------------------------- */
/* Phones only. On a desktop the header already carries the phone number and
   the button, so a second copy pinned to the screen is just clutter. */

.mbar { display: none; }

@media (max-width: 900px) {
  .mbar {
    display: block;
    position: fixed;
    inset-inline: 0;
    bottom: 0;
    z-index: 60;
    padding: 0.6rem 0.7rem;
    /* Without the safe area inset the bar sits under the home indicator on a
       modern iPhone and the bottom of every button is unreachable. */
    padding-bottom: calc(0.6rem + env(safe-area-inset-bottom, 0px));
    pointer-events: none;
  }

  .mbar-inner {
    pointer-events: auto;
    display: grid;
    grid-auto-flow: column;
    grid-auto-columns: 1fr;
    gap: 0.3rem;
    padding: 0.35rem;
    border-radius: 18px;
    border: 1px solid var(--line);
    background: rgba(20, 20, 23, 0.86);
  }

  .mbar-item {
    display: grid;
    justify-items: center;
    gap: 0.22rem;
    padding: 0.55rem 0.3rem;
    border-radius: 14px;
    color: var(--mute);
    font-size: 0.66rem;
    font-family: var(--display);
    letter-spacing: 0.03em;
    text-align: center;
    /* 44px is the smallest target a thumb hits reliably. */
    min-height: 44px;
    transition: color 0.2s var(--ease), background 0.2s var(--ease);
  }

  .mbar-item span {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    max-width: 100%;
  }

  .mbar-item:hover,
  .mbar-item:focus-visible { color: var(--bone); }

  .mbar-item--go {
    background: var(--bone);
    color: var(--ink);
  }
  .mbar-item--go:hover,
  .mbar-item--go:focus-visible { color: var(--ink); }

  /* The bar covers the last of the page, so give the footer room to clear it. */
  body { padding-bottom: 4.6rem; }

  .admin-bar .mbar { bottom: 0; }
}

@media (prefers-reduced-motion: reduce) {
  .mbar-item { transition: none; }
}

/* --- End of article ------------------------------------------------------ */
/* A reader who finishes a post had nowhere to go, which wastes the one moment
   they are most willing to read something else. */

/* The end-of-article block closes with its own breathing room, so the section
   around it does not need a full step underneath as well. Measured: 152px of
   section padding plus 91px of comment padding put 243px between the last
   card and the comment form. */
article .section--flush-top { padding-bottom: clamp(2rem, 4vw, 3.5rem); }

.post-end {
  margin-top: clamp(3rem, 7vw, 5.5rem);
  display: grid;
  gap: clamp(2.5rem, 5vw, 4rem);
  max-width: 54rem;
}

/* Share ------------------------------------------------------------------- */

.share {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem;
  padding-top: 2rem;
  border-top: 1px solid var(--line);
}

.share-label {
  font-family: var(--display);
  font-size: 0.7rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
  margin-right: 0.4rem;
}

.share-btn {
  font-family: var(--display);
  font-size: 0.8rem;
  color: var(--mute);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: 0.42em 0.95em;
  background: none;
  cursor: pointer;
  text-decoration: none;
  transition: color 0.2s var(--ease), border-color 0.2s var(--ease);
}

.share-btn:hover,
.share-btn:focus-visible { color: var(--bone); border-color: var(--mute); }
.share-btn[data-copied] { color: var(--ink); background: var(--bone); border-color: var(--bone); }

/* Author ------------------------------------------------------------------ */

.authorbox {
  display: grid;
  grid-template-columns: 4.5rem 1fr;
  gap: 1.5rem;
  padding: 1.75rem;
  border: 1px solid var(--glass-line);
  border-radius: 18px;
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  box-shadow: var(--glass-lip);
}

.authorbox-img { border-radius: 50%; width: 4.5rem; height: 4.5rem; }

.authorbox-name {
  font-size: var(--t-md);
  margin: 0.35rem 0 0.6rem;
}

.authorbox p { color: var(--mute); font-size: var(--t-sm); margin: 0; }

@media (max-width: 560px) {
  .authorbox { grid-template-columns: 1fr; gap: 1rem; }
}

/* Previous and next -------------------------------------------------------- */

.postnav {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 16rem), 1fr));
  gap: 1rem;
}

.postnav-item {
  display: grid;
  gap: 0.4rem;
  padding: 1.25rem 1.4rem;
  border-radius: 16px;
  text-decoration: none;
}

.postnav-item b {
  font-family: var(--display);
  font-size: var(--t-sm);
  color: var(--bone);
  line-height: 1.35;
}

.postnav-item--next { text-align: right; }

@media (max-width: 560px) {
  .postnav-item--next { text-align: left; }
}

/* Read next ---------------------------------------------------------------- */

.related-title {
  font-family: var(--display);
  font-size: 0.72rem;
  letter-spacing: 0.13em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 1.25rem;
}

.related-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  gap: 1rem;
}

.related-item {
  display: grid;
  gap: 0.5rem;
  align-content: start;
  padding: 1.25rem 1.4rem;
  border-radius: 16px;
  text-decoration: none;
}

.related-meta { font-size: 0.72rem; color: var(--faint); }
.related-item b { font-family: var(--display); color: var(--bone); font-size: var(--t-sm); line-height: 1.35; }
.related-item p { color: var(--mute); font-size: 0.82rem; margin: 0; line-height: 1.45; }

/* Copy on code ------------------------------------------------------------- */

.code-wrap { position: relative; }
.code-wrap pre { margin: 0; }

.code-copy {
  position: absolute;
  z-index: 2;
  top: 0.6rem;
  right: 0.6rem;
  font-family: var(--display);
  font-size: 0.68rem;
  letter-spacing: 0.06em;
  color: var(--faint);
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  -webkit-backdrop-filter: var(--glass-blur);
  backdrop-filter: var(--glass-blur);
  border: 1px solid var(--glass-line);
  border-radius: 8px;
  padding: 0.3em 0.7em;
  cursor: pointer;
  opacity: 0;
  transition: opacity 0.2s var(--ease), color 0.2s var(--ease);
}

/* Visible on hover, and always visible to a keyboard or a touch screen,
   where there is no hover to reveal it with. */
.code-wrap:hover .code-copy,
.code-copy:focus-visible { opacity: 1; }
.code-copy[data-copied] { color: var(--ink); background: var(--bone); border-color: var(--bone); opacity: 1; }

@media (hover: none) {
  .code-copy { opacity: 1; }
}

/* --- Contact form --------------------------------------------------------- */

.cform { display: grid; gap: 1.1rem; max-width: 44rem; }

.cform-row {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 14rem), 1fr));
  gap: 1.1rem;
}

.cform-field { display: grid; gap: 0.45rem; margin: 0; }

.cform-field label {
  font-family: var(--display);
  font-size: 0.78rem;
  letter-spacing: 0.04em;
  color: var(--mute);
}

.cform input,
.cform textarea {
  width: 100%;
  font: inherit;
  font-size: var(--t-sm);
  color: var(--bone);
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border: 1px solid var(--glass-line);
  border-radius: 12px;
  padding: 0.8rem 1rem;
  transition: border-color 0.2s var(--ease);
}

.cform textarea { resize: vertical; min-height: 9rem; }

.cform input:focus,
.cform textarea:focus {
  outline: none;
  border-color: var(--mute);
}

.cform input:focus-visible,
.cform textarea:focus-visible { outline: 2px solid var(--bone); outline-offset: 2px; }

.form-error { color: var(--bone); font-size: 0.8rem; }
.form-error--top {
  border: 1px solid var(--glass-line);
  border-radius: 12px;
  padding: 0.8rem 1rem;
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  box-shadow: var(--glass-lip);
}

/* The honeypot. Clipped rather than display:none, because some bots skip
   anything that is not rendered at all, and clipped rather than parked at
   -9999px, because that puts a real box far outside the document and can
   drag a horizontal scrollbar with it. */
.cform-trap {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

.cform-submit {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 1rem;
  margin: 0.4rem 0 0;
}

.cform-note { color: var(--faint); font-size: 0.82rem; max-width: 24rem; }

/* --- Detail cards --------------------------------------------------------- */
/* What used to be a table. Each row is a card, so nothing depends on column
   alignment and nothing has to scroll sideways on a phone. */

.specs { margin: 0; }

.specs-grid {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 19rem), 1fr));
  gap: 1rem;
}

/* A two column comparison reads better side by side than in a wide row, so a
   block with only one value per row gets a tighter track. */
.specs[data-cols="1"] .specs-grid {
  grid-template-columns: repeat(auto-fit, minmax(min(100%, 15rem), 1fr));
}

.spec {
  display: grid;
  align-content: start;
  gap: 0.9rem;
  padding: 1.5rem 1.6rem;
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  box-shadow: var(--glass-lip);
  transition: transform 0.3s var(--ease), border-color 0.3s var(--ease), background-color 0.3s var(--ease);
}

.spec:hover {
  transform: translateY(-3px);
  border-color: var(--glass-line-lit);
  background-color: rgba(30, 30, 35, 0.62);
}

.spec-title {
  font-family: var(--display);
  font-size: var(--t-md);
  color: var(--bone);
  margin: 0;
  line-height: 1.25;
}

.spec-list { margin: 0; display: grid; gap: 0.85rem; }

.spec-pair { display: grid; gap: 0.2rem; }

.spec-pair dt {
  font-family: var(--display);
  font-size: 0.66rem;
  letter-spacing: 0.12em;
  text-transform: uppercase;
  color: var(--faint);
}

.spec-pair dd {
  margin: 0;
  color: var(--mute);
  font-size: var(--t-sm);
  line-height: 1.5;
}

/* The first value is the one being argued for, so it carries the weight. */
.spec-pair--lead dd { color: var(--bone); }

.specs-note {
  color: var(--faint);
  font-size: var(--t-sm);
  margin: 1.1rem 0 0;
}

/* --- Cards arriving on scroll -------------------------------------------- */
/* Each card in a grid comes in just behind the one before it. The stagger is
   capped so a long grid never leaves the reader waiting on the last card, and
   it is driven by an index the render sets rather than by a script. */

.has-js [data-reveal] .spec,
.has-js [data-reveal] .work,
.has-js [data-reveal] .svc,
.has-js [data-reveal] .related-item,
.has-js [data-reveal] .feature {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity 0.45s var(--ease), transform 0.45s var(--ease);
  transition-delay: calc(min(var(--i, 0), 5) * 60ms);
}

.has-js [data-reveal].is-in .spec,
.has-js [data-reveal].is-in .work,
.has-js [data-reveal].is-in .svc,
.has-js [data-reveal].is-in .related-item,
.has-js [data-reveal].is-in .feature {
  opacity: 1;
  transform: none;
}

@media (prefers-reduced-motion: reduce) {
  .has-js [data-reveal] :is(.spec, .work, .svc, .related-item, .feature) {
    opacity: 1;
    transform: none;
    transition: none;
  }
  .spec:hover { transform: none; }
}

/* --- Placeholder marker ------------------------------------------------- */
/* Anything the client must replace before launch is visibly flagged.      */

.todo {
  background: rgba(255, 255, 255, 0.10);
  color: var(--bone);
  border: 1px dashed var(--mute);
  border-radius: 0.35rem;
  padding: 0.05em 0.45em;
  font-family: var(--display);
  font-size: 0.85em;
}

/* --- Reveal on scroll --------------------------------------------------- */
/* One orchestrated reveal per section, not an effect on every element.    */

/* Scoped to .has-js, which main.js sets on <html> before first paint. If the
   script fails to load or throws, the class is never set and every section
   stays visible. Hiding content by default and relying on JS to bring it back
   means one broken script is a blank page. */

.has-js [data-reveal] {
  opacity: 0;
  transform: translateY(10px);
  /* Was 0.7s. A section that takes most of a second to arrive is a section
     the reader is waiting for, and fifteen of them in a row is what "the page
     feels laggy" actually describes. The frame rate was never the problem. */
  transition: opacity 0.34s var(--ease), transform 0.34s var(--ease);
}
.has-js [data-reveal].is-in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.01ms !important;
  }
  .has-js [data-reveal] { opacity: 1; transform: none; }
  .marquee-track { animation: none; }
}

/* ==========================================================================
   WordPress
   Classes that only exist once this is a theme rather than a prototype:
   post content, pagination, forms, and the accessibility helpers core
   expects to be present.
   ========================================================================== */

/* --- Screen reader text -------------------------------------------------- */
/* Core and several plugins output this class and assume the theme styles it.
   Without it, labels intended only for screen readers appear on the page. */

.screen-reader-text {
  border: 0;
  clip-path: inset(50%);
  height: 1px;
  width: 1px;
  margin: -1px;
  overflow: hidden;
  padding: 0;
  position: absolute;
  white-space: nowrap;
}
.screen-reader-text:focus {
  clip-path: none;
  height: auto;
  width: auto;
  margin: 0;
  padding: 0.75rem 1.25rem;
  position: fixed;
  top: 1rem;
  left: 1rem;
  z-index: 1000;
  background: var(--accent);
  color: var(--ink);
  font-family: var(--display);
  font-weight: 700;
}

/* --- Long-form post content ---------------------------------------------- */

/* --- Post layout --------------------------------------------------------- */
/* The reading column stays at 42rem because a longer line measures worse, so
   the space to its right goes to the contents list rather than to a wider
   paragraph. */

.post-grid {
  display: grid;
  /* The article takes whatever is left rather than a fixed measure, so the
     column actually fills the page instead of leaving a quarter of it empty
     beside the contents list. Capped below so it does not run away on a very
     wide monitor. */
  grid-template-columns: minmax(0, 1fr) minmax(0, 17rem);
  gap: clamp(2rem, 4vw, 4rem);
  align-items: start;
}

/* The reading column is wider here than the 42rem default, and the elements
   that benefit from width break past it entirely rather than being squeezed
   into the measure that suits prose. A code block or a comparison table wants
   room; a paragraph does not. */
.post-grid .prose { max-width: 54rem; }

.post-grid .prose > :is(figure, .wp-block-table, pre, .code-wrap, .wp-block-image) {
  width: calc(100% + 6rem);
  max-width: calc(100% + 6rem);
  margin-inline: -3rem;
}

@media (max-width: 1280px) {
  .post-grid .prose > :is(figure, .wp-block-table, pre, .code-wrap, .wp-block-image) {
    width: 100%;
    max-width: 100%;
    margin-inline: 0;
  }
}

.post-aside { position: sticky; top: 7.5rem; }

/* No max-height and no overflow. A navigation aid the reader has to scroll is
   not saving anyone anything, so the list is trimmed to fit instead. */
.toc {
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border: 1px solid var(--glass-line);
  border-radius: 16px;
  box-shadow: var(--glass-lip);
  padding: 1.4rem 1.35rem;
}

.toc-title {
  font-family: var(--display);
  font-size: 0.7rem;
  letter-spacing: 0.14em;
  text-transform: uppercase;
  color: var(--faint);
  margin: 0 0 1.25rem;
}

.toc ol {
  list-style: none;
  margin: 0;
  padding: 0;
  display: grid;
  gap: 0.1rem;
  counter-reset: toc;
}

.toc li { counter-increment: toc; }

.toc a {
  display: grid;
  grid-template-columns: 1.6rem 1fr;
  gap: 0.3rem;
  padding: 0.5rem 0;
  color: var(--mute);
  font-size: 0.95rem;
  line-height: 1.4;
  text-decoration: none;
  transition: color 0.2s var(--ease);
}

/* The number gives the reader a sense of how far through they are, which a
   bare list of links does not. */
.toc a::before {
  content: counter(toc, decimal-leading-zero);
  font-family: var(--display);
  font-size: 0.7rem;
  color: var(--line);
  padding-top: 0.25em;
  transition: color 0.2s var(--ease);
}

.toc a:hover,
.toc a:focus-visible { color: var(--bone); }
.toc a:hover::before { color: var(--faint); }

/* The section currently on screen, set by main.js. */
.toc a[data-here="true"] { color: var(--bone); }
.toc a[data-here="true"]::before { color: var(--bone); }

.toc .toc-l3 a { padding-left: 0.9rem; font-size: 0.86rem; color: var(--faint); }

/* An anchor jump must not land the heading under the fixed header. */
.prose :is(h2, h3)[id] { scroll-margin-top: 7rem; }

/* Standalone controls need a 44px target on a phone. Measured on the article
   page: contents links 37px, share buttons 34px, code copy 24px. Inline links
   inside a paragraph are the recognised exception, since padding them would
   break the line they sit in. */
@media (max-width: 768px) {
  .toc a { min-height: 44px; align-items: center; }
  .share-btn { min-height: 44px; display: inline-flex; align-items: center; }
  .code-copy { min-height: 44px; display: inline-flex; align-items: center; padding-inline: 0.95em; }
  .postnav-item, .related-item { min-height: 44px; }
}

@media (max-width: 1080px) {
  .post-grid { grid-template-columns: 1fr; }

  /* Above the article on a narrow screen, where it is read first rather than
     missed entirely. */
  .post-aside { position: static; order: -1; margin-bottom: 2.5rem; }

  .toc {
    border-left: 0;
    border: 1px solid var(--line);
    border-radius: 16px;
    padding: 1.25rem 1.4rem;
  }

  .toc ol { gap: 0; }
}

.prose { max-width: 42rem; }
.prose > * { margin-bottom: 1.25em; }
.prose h2 { font-size: var(--t-lg); margin-top: 2.5em; margin-bottom: 0.6em; }
.prose h3 { font-size: var(--t-md); margin-top: 2em; margin-bottom: 0.5em; }
.prose p, .prose li { color: var(--mute); }
.prose strong { color: var(--bone); }
.prose a { color: var(--bone); text-decoration: underline; text-underline-offset: 3px; }
.prose a:hover { text-decoration-thickness: 2px; }
.prose ul, .prose ol { padding-left: 1.25rem; }
.prose li { margin-bottom: 0.5em; }
.prose img { border-radius: 0.75rem; }
.prose blockquote {
  margin: 2em 0;
  padding-left: 1.5rem;
  border-left: 2px solid var(--line);
  font-family: var(--display);
  font-size: var(--t-md);
  color: var(--bone);
}
.prose code {
  font-family: ui-monospace, "SF Mono", Menlo, monospace;
  font-size: 0.9em;
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border: 1px solid var(--glass-line);
  border-radius: 0.3rem;
  padding: 0.1em 0.35em;
}
.prose pre {
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border: 1px solid var(--glass-line);
  box-shadow: var(--glass-lip);
  border-radius: 0.75rem;
  padding: 1.25rem;
  overflow-x: auto;
}

/* A code block inside a pre already has the panel behind it. Painting the
   inline style again puts a box round every line. */
.prose pre code {
  background: none;
  border: 0;
  padding: 0;
}
.prose pre code { background: none; border: 0; padding: 0; }

.post-hero-img { border-radius: 1.25rem; margin-bottom: 1rem; }

.post-meta {
  display: flex;
  gap: 0.5rem;
  align-items: center;
  font-size: var(--t-sm);
  color: var(--faint);
  margin-top: 1.25rem;
}

/* --- Pagination ---------------------------------------------------------- */

.pagination { margin-top: 3.5rem; }
.pagination .nav-links { display: flex; flex-wrap: wrap; gap: 0.5rem; }
.pagination .page-numbers {
  display: inline-grid;
  place-items: center;
  min-width: 2.6rem;
  height: 2.6rem;
  padding-inline: 0.85rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  font-family: var(--display);
  font-size: var(--t-sm);
  color: var(--mute);
  transition: border-color 0.2s var(--ease), color 0.2s var(--ease);
}
.pagination .page-numbers:hover { border-color: var(--accent); color: var(--bone); }
.pagination .page-numbers.current {
  background: var(--accent);
  border-color: var(--accent);
  color: var(--ink);
}
.page-links { margin-top: 2rem; display: flex; gap: 0.5rem; align-items: center; }

/* --- Search form --------------------------------------------------------- */

.search-wrap { max-width: 28rem; margin-top: 2rem; }
.search-form { display: flex; gap: 0.5rem; }
.search-field {
  flex: 1;
  min-width: 0;
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border: 1px solid var(--glass-line);
  border-radius: 999px;
  color: var(--bone);
  font-family: var(--body);
  font-size: var(--t-base);
  padding: 0.8rem 1.25rem;
}
.search-field::placeholder { color: var(--faint); }
.search-field:focus {
  outline: none;
  border-color: var(--accent);
}
.search-form .btn { flex: none; }

/* --- Alignment ----------------------------------------------------------- */

.alignwide { max-width: 1440px; margin-inline: auto; }
.alignfull { max-width: none; margin-inline: calc(50% - 50vw); width: 100vw; }
.aligncenter { margin-inline: auto; }
.alignleft { float: left; margin: 0 1.5rem 1rem 0; }
.alignright { float: right; margin: 0 0 1rem 1.5rem; }

/* --- Grid toggle --------------------------------------------------------- */
/* Set from the Customizer. Turning it off removes the background entirely
   rather than setting the colour to transparent, so nothing is painted. */

body.no-grid { background-image: none; }

/* --- Logo ---------------------------------------------------------------- */

.logo-img {
  width: var(--logo-w, 168px);
  height: auto;
  display: block;
}

/* --- Comments ------------------------------------------------------------ */

.comment-list { list-style: none; margin: 0; padding: 0; }
.comment-list li { border-top: 1px solid var(--line); padding-block: 1.5rem; }
.comment-list .children { list-style: none; padding-left: 1.5rem; }
.comment-form input:not([type="submit"]),
.comment-form textarea {
  width: 100%;
  background-color: var(--glass-tint);
  background-image: var(--glass-sheen);
  border: 1px solid var(--glass-line);
  border-radius: 0.75rem;
  color: var(--bone);
  font-family: var(--body);
  padding: 0.8rem 1rem;
  margin-bottom: 1rem;
}
.comment-form label { display: block; font-size: var(--t-sm); color: var(--mute); margin-bottom: 0.35rem; }
