/* ==========================================================================
   Azure Legal PLLC — main stylesheet
   Design: "Network" (Direction 9) — a presentation-style landing page with
   a Presentation mode (one slide, full-viewport, crossfade) and a Slide
   view mode (PowerPoint-style editor window with thumbnails).
   ========================================================================== */

:root {
  --porcelain: #F5F8FC;
  --porcelain-2: #FFFFFF;
  --porcelain-3: #E7EEF7;
  --porcelain-4: #B7C8E3;
  --ink: #0F2447;
  --ink-2: #3A4E70;
  --ink-3: #6F7F9C;
  --azure: #0A84FF;
  --azure-2: #0066CC;
  --azure-tint: #E6F1FF;
  --red: #C4304B;                        /* form/validation error text */
  --kicker: var(--ink-2);                /* darker, more legible than --ink-3 on porcelain */

  /* Surfaces that used to be literal colours. Named by role so the dark
     theme below can remap them in one place. */
  --ground: var(--porcelain);            /* page background */
  --chrome: rgba(245, 248, 252, .82);    /* frosted fixed header */
  --chrome-2: rgba(245, 248, 252, .9);   /* frosted progress-label backing */
  --glass: rgba(255, 255, 255, .7);      /* translucent pills / toggle */
  --row-hover: rgba(255, 255, 255, .6);
  --shadow: rgba(15, 36, 71, .35);
  /* Solid, not translucent — this used to carry an alpha value, and at
     ~96-97% opacity the fixed-position backdrop let high-contrast slide
     text sitting directly behind it (a bold headline, a tagline) ghost
     through faintly at some scroll positions in Slide view. Fully opaque
     removes that without changing how it looks anywhere else. */
  --overlay: #0F2447;
  --desk: #DCE3EC;                       /* Slide view: desk behind the window */
  --win: #EEF2F7;                        /* Slide view: editor window */
  --win-border: #C9D3E0;
  --win-bar: #FFFFFF;
  --win-rule: #DCE3EC;
  --thumbs: #F7F9FC;
  --thumb: #FFFFFF;
  --thumb-border: #D5E1F0;
  --on-ink: #fff;                        /* text on an --ink fill */

  --display: "Sora", "Helvetica Neue", Arial, sans-serif;
  --body: "Source Sans 3", "Helvetica Neue", Arial, sans-serif;
  --mono: "Overpass Mono", "SFMono-Regular", Consolas, monospace;

  --pad: clamp(20px, 5vw, 84px);
  --top: 64px;
  --foot: 40px;
}

/* ---------- dark theme ----------
   The brand's ink ground (see brand/): deep ink field, azure as the light.
   Applied via html[data-theme="dark"], which an inline script in <head>
   sets before first paint from localStorage or prefers-color-scheme. The
   tokens are inverted by role — porcelain becomes the ink field, ink
   becomes the type — so every component picks it up without its own
   dark rule. Only the wordmark chip and the network canvas change
   construction, not just colour. */
html[data-theme="dark"] {
  color-scheme: dark;
  --porcelain: #0A1628;
  --porcelain-2: #111F3B;
  --porcelain-3: #172A4C;
  --porcelain-4: #243A5E;
  --ink: #F5F8FC;
  --ink-2: #B9C5DA;
  --ink-3: #8393AE;
  --kicker: var(--ink-3);                /* unchanged from before — this review was light-theme only */
  --azure-2: #3D9BFF;                    /* hover goes lighter, not darker, on ink */
  --azure-tint: rgba(10, 132, 255, .22);
  --red: #E0637A;                        /* lighter on ink, same reasoning as --azure-2 */

  --ground: linear-gradient(135deg, #0A1628, #040B1C) fixed;
  --chrome: rgba(8, 18, 36, .78);
  --chrome-2: rgba(8, 18, 36, .9);
  --glass: rgba(255, 255, 255, .06);
  --row-hover: rgba(255, 255, 255, .04);
  --shadow: rgba(0, 0, 0, .6);
  --overlay: #040B1C;
  --desk: #040B1C;
  --win: #0C1A33;
  --win-border: #223656;
  --win-bar: #101F3D;
  --win-rule: #1C2F4F;
  --thumbs: #0E1C36;
  --thumb: #0A1628;
  --thumb-border: #243A5E;
  --on-ink: #0A1628;
}

* { box-sizing: border-box; }
/* overflow:hidden must be on html AND body — body's overflow alone isn't
   guaranteed to propagate to the viewport in every browser, and any stray
   sub-pixel/layout overflow (e.g. from a scrollbar-induced reflow) then
   creates a real, tiny native page scroll. That collides with the wheel
   handler below, which treats wheel events as slide navigation — the
   result looks like the deck randomly snapping back to the cover slide. */
html { height: 100%; overflow: hidden; }
body {
  height: 100%;
  margin: 0;
  background: var(--ground);
  color: var(--ink);
  font-family: var(--body);
  -webkit-font-smoothing: antialiased;
  overflow: hidden;
}
/* Not overflow:auto here (only re-enabled below, at the mobile
   breakpoint where .slide switches to a natural, possibly-taller-than-
   viewport height): .window's height is a definite calc() sized to fit
   the viewport exactly above that breakpoint, with .work and .thumbs
   each scrolling internally if their own content runs long. Any
   sub-pixel mismatch in that calc used to create a page-level scroll —
   and since .top is a fixed header, scrolling even a few px dragged
   .window's rounded top corner up behind it. */
body.view-all { overflow: hidden; }
a { color: inherit; text-decoration: none; }
:focus-visible { outline: 2px solid var(--azure); outline-offset: 3px; }

.skip {
  position: absolute; left: -999px; top: 8px;
  background: var(--ink); color: var(--on-ink); padding: 8px 12px;
  z-index: 50; font-family: var(--mono); font-size: 12px;
}
.skip:focus { left: 8px; }

canvas#net {
  position: fixed; inset: 0; width: 100%; height: 100%;
  z-index: 0; pointer-events: none; opacity: 0; transition: opacity .8s;
}
canvas#net.on { opacity: 1; }
/* Slide view gives the active slide its own confined .slide-net instead
   (see below) — the page-level canvas showing through the window's
   transparent background on top of that would be a second, independent
   animation layered behind it for no visual benefit, so it's hidden
   whenever Slide view is on. */
body.view-all canvas#net { opacity: 0; }

/* ---------- chrome ---------- */
.top {
  /* Opaque (frosted) on purpose: this bar sits fixed above content that
     genuinely scrolls underneath it in Slide view and on mobile (both
     use real page scroll), and with no background at all, scrolled-past
     text visibly passed right through it as it crossed the top of the
     viewport. Presentation mode never scrolls content past this bar (a
     slide's own top padding keeps it clear), so the frosted look costs
     nothing there and just adds a bit of polish. */
  position: fixed; top: 0; left: 0; right: 0; height: var(--top);
  display: flex; align-items: center; justify-content: space-between;
  padding: 0 var(--pad); z-index: 30; pointer-events: none;
  background: var(--chrome);
  -webkit-backdrop-filter: blur(10px);
  backdrop-filter: blur(10px);
  border-bottom: 1px solid var(--porcelain-4);
}
.top > * { pointer-events: auto; }
.brand {
  font-family: var(--display); font-weight: 600; font-size: 15px;
  letter-spacing: -.01em; display: flex; align-items: center; gap: 10px;
}
/* The Inguz rune mark: a tall, narrow glyph (viewBox ~103x238) rather than
   the roughly-square icon a header mark usually is — sized by height, not
   width, so it stays true to its actual proportions instead of being
   squashed into a square slot. */
.brand .mark { height: 30px; width: auto; flex: none; transition: filter .25s ease-out; }
.brand .hdr-k { font-size: 12px; }
/* Header-only: drop the hairline border dark theme otherwise puts on the
   chip (html[data-theme="dark"] .wordmark .chip) — everywhere else that
   still keeps it. Selector weight (.top .brand .hdr-wm) is deliberate, to
   outrank that rule's own specificity. */
.top .brand .hdr-wm .chip { box-shadow: none; }
.right { display: flex; align-items: center; gap: 12px; }

.toggle {
  display: flex; border: 1px solid var(--porcelain-4); border-radius: 999px;
  padding: 3px; background: var(--glass);
  font-family: var(--mono); font-size: 11px; letter-spacing: .05em; text-transform: uppercase;
}
.toggle button {
  border: 0; background: transparent; color: var(--ink-3); padding: 7px 12px;
  border-radius: 999px; cursor: pointer; font: inherit; letter-spacing: inherit;
  text-transform: inherit; display: flex; align-items: center; gap: 7px;
  transition: background .15s, color .15s;
}
.toggle button svg { width: 12px; height: 12px; }
.toggle button[aria-pressed="true"] { background: var(--porcelain-3); color: var(--ink-2); }
.toggle button:not([aria-pressed="true"]):hover { color: var(--ink); }

/* light / dark switch */
.theme {
  width: 36px; height: 36px; border-radius: 50%; border: 1px solid var(--porcelain-4);
  background: var(--glass); color: var(--ink-3); cursor: pointer; display: grid; place-items: center;
  transition: border-color .15s, color .15s;
}
.theme:hover { border-color: var(--azure); color: var(--azure); }
.theme svg { width: 15px; height: 15px; }
.theme .sun { display: none; }
html[data-theme="dark"] .theme .sun { display: block; }
html[data-theme="dark"] .theme .moon { display: none; }

.btn {
  font-family: var(--display); font-weight: 600; font-size: 13.5px;
  padding: 12px 20px; background: var(--azure); color: #fff; border-radius: 999px;
  display: inline-flex; align-items: center; gap: 10px; border: 0; cursor: pointer;
  transition: background .15s; white-space: nowrap;
}
.btn:hover { background: var(--azure-2); }
.btn.line { background: transparent; color: var(--ink); box-shadow: inset 0 0 0 1px var(--porcelain-4); }
.btn.line:hover { box-shadow: inset 0 0 0 1px var(--azure); color: var(--azure); background: transparent; }
.btn.lg { font-size: 15px; padding: 15px 26px; }

/* segmented progress */
.segs {
  /* Same category of bug as .top originally had: this bar (and, worse,
     its hover/active label, which floats 10px below it with no
     background of its own) sits fixed above content that genuinely
     scrolls underneath it in Slide view/mobile. Without a backdrop, a
     label like "The problem" rendered directly on top of whatever
     scrolled-past text happened to be sitting at that exact height —
     confirmed live, "Azure Legal" showing straight through it. ::before
     gives just the label's danger zone (not the full .top-style bar,
     since the 2px track itself is thin enough not to need it) an opaque
     frosted backing, sized to the label's actual position/height.
     Height is a real hit target (16px, grown symmetrically around the
     old 2px line via top: -14px instead of -6px) even though the drawn
     track stays the original 2px — a 2px-tall click target was very
     hard to land on precisely. The extra height sits inside .top's own
     64px band, not over slide content, so it doesn't steal clicks from
     anything below. */
  position: fixed; top: calc(var(--top) - 14px); left: var(--pad); right: var(--pad);
  display: flex; gap: 6px; height: 16px; z-index: 30;
}
.segs::before {
  /* Full viewport width (fixed, not inset off .segs's own pad-inset box)
     so its edge lines up with .top's — .top spans edge-to-edge, and the
     old left/right:-10px (relative to .segs, which starts at var(--pad))
     stopped short of that, leaving a visible seam where the two frosted
     backings' edges didn't meet, right where .top's bottom border ends. */
  content: ""; position: fixed; left: 0; right: 0; top: calc(var(--top) - 10px); height: 34px;
  background: var(--chrome-2);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  z-index: -1; pointer-events: none;
}
.segs a { flex: 1; position: relative; }
/* The drawn track: 2px, positioned at the same absolute spot the old
   2px-tall anchor used to occupy, so enlarging the click target above
   doesn't move anything visually. */
.segs a::before { content: ""; position: absolute; left: 0; right: 0; top: 8px; height: 2px; background: var(--porcelain-4); border-radius: 2px; }
.segs a::after {
  content: ""; position: absolute; left: 0; top: 8px; width: 100%; height: 2px; background: var(--azure); border-radius: 2px;
  transform: scaleX(0); transform-origin: left; transition: transform .45s ease;
}
.segs a.done::after, .segs a[aria-current="true"]::after { transform: scaleX(1); }
.segs a span {
  position: absolute; left: 0; top: 18px; font-family: var(--mono); font-size: 10px;
  letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); white-space: nowrap;
  opacity: 0; transition: opacity .2s;
}
.segs a:hover span, .segs a:focus-visible span, .segs a[aria-current="true"] span { opacity: 1; }
@media (prefers-reduced-motion: reduce) { .segs a::after { transition: none; } }

/* bottom: index link + arrows */
.indexlink {
  position: fixed; left: var(--pad); bottom: calc(var(--foot) + 32px); z-index: 30;
  font-family: var(--mono); font-size: 11.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink); display: flex; align-items: center; gap: 12px;
  background: transparent; border: 0; cursor: pointer; padding: 6px 0;
  transition: color .15s;
}
.indexlink b { color: inherit; font-weight: 400; }
.indexlink svg { width: 16px; height: 16px; color: inherit; transition: color .15s; }
.indexlink:hover, .indexlink:hover b, .indexlink:hover svg { color: var(--azure); }
.indexlink .u { border-bottom: 1px solid var(--porcelain-4); padding-bottom: 2px; }
.indexlink:hover .u { border-bottom-color: var(--azure); }

.ctl { position: fixed; right: var(--pad); bottom: calc(var(--foot) + 28px); z-index: 30; display: flex; align-items: center; gap: 10px; }
.arr {
  width: 44px; height: 44px; border-radius: 50%; border: 1px solid var(--porcelain-4);
  background: var(--porcelain-2); color: var(--ink); cursor: pointer;
  font-family: var(--mono); font-size: 15px; display: grid; place-items: center;
  transition: border-color .15s, color .15s;
}
.arr:hover { border-color: var(--azure); color: var(--azure); }
.arr:disabled { opacity: .3; cursor: default; }

/* index overlay */
.overlay {
  position: fixed; inset: 0; z-index: 40; overflow: hidden;
  /* A radial azure bloom in one corner instead of a flat backdrop — the
     same "light source" idea the cover slide's network canvas uses, so
     this reads as part of the brand rather than a generic modal.
     Longhand background-color/-image, not the shorthand — mixing a plain
     color into a comma-separated background shorthand list is invalid
     (color isn't a <bg-image>), and silently drops the color layer in
     some engines rather than falling back to it, leaving the backdrop
     translucent instead of solid. */
  background-color: var(--overlay);
  background-image: radial-gradient(ellipse 900px 700px at 88% 8%, rgba(10,132,255,.22), transparent 60%);
  color: #fff; display: flex; align-items: center; justify-content: center;
  opacity: 0; visibility: hidden; transition: opacity .25s, visibility .25s;
}
.overlay.open { opacity: 1; visibility: visible; }
/* Giant watermark of the rune mark itself, bleeding off the bottom-right
   corner — decorative only (aria-hidden), low opacity so it never
   competes with the slide list sitting on top of it. */
.overlay-mark {
  position: absolute; bottom: -12%; right: -6%; height: 65vh; width: auto;
  opacity: .07; pointer-events: none;
}
.overlay-brand {
  position: absolute; top: 22px; left: var(--pad); display: flex; align-items: center; gap: 12px;
}
.overlay-brand-mark { height: 34px; width: auto; }
.overlay-brand-text { display: flex; flex-direction: column; gap: 2px; }
.overlay-brand-name { font-family: var(--display); font-weight: 600; font-size: 17px; letter-spacing: -.01em; }
.overlay-brand-name b { color: var(--azure); font-weight: 600; }
.overlay-brand-tag { font-family: var(--mono); font-size: 10px; letter-spacing: .08em; text-transform: uppercase; color: rgba(255,255,255,.45); }
.overlay ol { list-style: none; margin: 0; padding: 0; display: grid; grid-template-columns: repeat(2, max-content); gap: 4px 48px; }
@media (min-width: 961px) {
  .overlay ol { grid-auto-flow: column; grid-template-rows: repeat(var(--menu-rows, 6), auto); }
}
.overlay li a {
  display: grid; grid-template-columns: 52px max-content; gap: 16px; align-items: baseline;
  padding: 12px 10px; border-radius: 8px; font-family: var(--display); font-weight: 300;
  font-size: clamp(28px, 3vw, 40px); letter-spacing: -.01em; white-space: nowrap;
  transition: transform .2s ease;
}
/* Titles read as a bright azure-to-white gradient rather than flat white —
   the same "light catching a cold surface" idea as the cover mark's own
   gradient stroke, so this list feels like part of the brand rather than
   a plain index. Current/hover collapses to solid azure with a soft glow:
   a clear single accent instead of competing with the gradient at rest. */
.overlay li a {
  background-image: linear-gradient(100deg, #eaf3ff 0%, var(--azure) 65%, var(--azure-2) 100%);
  -webkit-background-clip: text; background-clip: text; color: transparent;
}
.overlay li a span { font-family: var(--mono); font-size: 16px; color: rgba(255,255,255,.5); background: none; -webkit-background-clip: initial; background-clip: initial; -webkit-text-fill-color: initial; }
.overlay li a:hover, .overlay li a[aria-current="true"] { background-color: rgba(255,255,255,.08); transform: translateX(2px); }
.overlay li a:hover, .overlay li a:hover span, .overlay li a[aria-current="true"], .overlay li a[aria-current="true"] span {
  background-image: none; -webkit-text-fill-color: initial; color: var(--azure); text-shadow: 0 0 18px rgba(10,132,255,.55);
}
.overlay .close {
  position: absolute; top: 18px; right: var(--pad); font-family: var(--mono); font-size: 12px;
  letter-spacing: .06em; text-transform: uppercase; background: transparent;
  border: 1px solid rgba(255,255,255,.3); color: #fff; padding: 9px 14px; border-radius: 999px; cursor: pointer;
}

/* ---------- slides ---------- */
.stage { position: relative; z-index: 1; height: 100vh; height: 100svh; }
.slide {
  position: absolute; inset: 0; padding: 84px var(--pad) calc(var(--foot) + 96px);
  display: flex; flex-direction: column;
  /* "safe center" instead of plain "center": a slide with enough content
     to exceed the viewport still centers when it fits, but when it
     doesn't, falls back to start-alignment instead of overflowing off
     both edges equally — plain "center" was pushing the kicker ("04 Who
     we serve" etc.) above scroll position 0 on content-heavy slides,
     making it unreachable/invisible rather than just scrollable to. */
  justify-content: safe center;
  opacity: 0; visibility: hidden; transform: translateY(12px);
  transition: opacity .45s ease, transform .45s ease, visibility .45s; overflow: auto;
}
.slide.active { opacity: 1; visibility: visible; transform: none; }
@media (prefers-reduced-motion: reduce) { .slide { transition: none; transform: none; } }

/* Slide view: an editor window with a thumbnail pane and 16:9 slide cards */
body.view-all { background: var(--desk); }
/* Network canvas stays visible here too — it only shows through the grey
   gaps around/behind the window and between cards, since every card and
   the window itself have their own opaque background, so it never
   competes with actual slide content. */
body.view-all .stage {
  /* Horizontal gutter (not just top/bottom) so .window never touches the
     viewport edges at any scroll depth — without it, .window's own
     max-width (1500px) plus a typical desktop viewport left almost no
     visible gap for the network canvas to show through; the "editor
     window floating on a canvas" look needs that gap to actually read.
  */
  /* Top and bottom both add 40px beyond the fixed header/footer's own
     height, so the empty gap around .window reads the same on both
     edges — a +12px top term here used to leave only a 12px gap below
     the header while the bottom had a full 40px below the footer,
     looking lopsided (no visible breathing room up top). */
  height: auto; padding: calc(var(--top) + 40px) var(--pad) calc(var(--foot) + 40px);
}
body.view-all .window {
  /* Fixed to the actual available viewport height (mirrors .stage's own
     top/bottom padding above) rather than left auto/intrinsic — a grid
     row sized "1fr" only distributes real space if the grid container
     itself has a definite height; without this, .window (and therefore
     .work, and therefore .slide) just grew to fit whatever height the
     slide's content happened to need, so a short slide's card ended up
     much shorter than the available window with a big gap underneath
     instead of filling it, same as Presentation mode's own slides always
     fill the full screen regardless of how much content they hold. */
  max-width: 1500px; margin: 0 auto; background: var(--win); border: 1px solid var(--win-border);
  border-radius: 12px; box-shadow: 0 30px 60px -30px var(--shadow);
  display: grid; grid-template-columns: 232px 1fr; grid-template-rows: auto 1fr;
  height: calc(100svh - var(--top) - 40px - var(--foot) - 40px);
}
body.view-all .titlebar {
  grid-column: 1 / -1; display: flex; align-items: center; justify-content: space-between;
  padding: 10px 16px; background: var(--win-bar); border-bottom: 1px solid var(--win-rule);
  border-radius: 12px 12px 0 0;
  font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3);
}
body.view-all .titlebar b { color: var(--ink); font-weight: 400; }
body.view-all .titlebar .dots { display: flex; gap: 6px; }
body.view-all .titlebar .dots i { width: 9px; height: 9px; border-radius: 50%; background: var(--thumb-border); }
body.view-all .titlebar .dots i:first-child { background: var(--azure); }
body.view-all .thumbs {
  /* Sized to its actual grid row (row 2, same as .work — default
     align-self:stretch) rather than its own independent viewport calc:
     that calc didn't account for .foot the way .window's own height
     does, so it allowed a taller box than the window actually has room
     for, and — since .window itself is overflow:visible (only .work
     clips) — the rail spilled out past the window's bottom edge instead
     of scrolling internally. */
  display: flex; flex-direction: column; gap: 10px; padding: 14px 12px; background: var(--thumbs);
  border-right: 1px solid var(--win-rule); overflow-y: auto;
  /* .work already rounds its own bottom-right corner to match .window's
     (see body.view-all .work below) — this is the equivalent for the
     bottom-left, which .window shares with .thumbs the same way. Without
     it, .thumbs' square corner sat visibly inside .window's rounded one. */
  border-radius: 0 0 0 12px;
}
body.view-all .thumb {
  display: grid; grid-template-columns: 18px 1fr; grid-template-rows: auto auto; gap: 2px 8px; align-items: start;
  background: transparent; border: 0; padding: 0; cursor: pointer; text-align: left; font: inherit; color: inherit;
}
/* Without explicit placement, grid auto-flow puts this 3rd child (.tn,
   .tf, then .tt) in row 2 starting from column 1 — the narrow 18px
   number column — wrapping the title text into a sliver a couple of
   characters wide instead of under the thumbnail image where it reads
   as one line. .tf and .tt both pinned to column 2 fixes that. */
body.view-all .thumb .tn { grid-column: 1; grid-row: 1; font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); padding-top: 2px; }
body.view-all .thumb .tf {
  grid-column: 2; grid-row: 1;
  aspect-ratio: 16 / 9; background: var(--thumb); border: 1.5px solid var(--thumb-border); border-radius: 4px;
  overflow: hidden; position: relative; transition: border-color .15s, box-shadow .15s;
  display: flex; align-items: center; justify-content: center;
}
/* The CURRENT page's own thumb clones its .wrap directly (content really
   is in this document already) — padded/centered the same way .slide
   itself centers its .wrap. Every OTHER page's thumb is a sandboxed
   iframe rendering that whole page for real (see buildThumbs()), so it
   gets its own rule below without the padding/centering, which would
   just shrink the iframe's simulated viewport for no reason — the page
   inside already centers its own content exactly the way this page does. */
.thumb .tf .mini {
  position: absolute; left: 0; top: 0; width: 1600px; height: 900px; transform-origin: 0 0;
  padding: 60px 90px; display: flex; flex-direction: column; justify-content: center;
  pointer-events: none; background: var(--porcelain);
}
.thumb .tf .mini .wrap { gap: 28px; }
.thumb .tf .mini.mini-frame { padding: 0; display: block; border: 0; }
body.view-all .thumb .tt {
  grid-column: 2; grid-row: 2;
  font-family: var(--mono); font-size: 9.5px; letter-spacing: .04em; color: var(--ink-3);
  margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
body.view-all .thumb[aria-current="true"] .tf { border-color: var(--azure); box-shadow: 0 0 0 2px var(--azure-tint); }
body.view-all .thumb[aria-current="true"] .tn, body.view-all .thumb[aria-current="true"] .tt { color: var(--azure); }
body.view-all .thumb:hover .tf { border-color: var(--azure); }
body.view-all .work {
  padding: 26px clamp(16px, 3vw, 40px) 40px; display: flex; flex-direction: column; gap: 28px;
  min-width: 0; min-height: 0; border-radius: 0 0 12px 0;
  /* .slide's own layout box stays its full unscaled 1600x900 (transform
     doesn't shrink that, only how it paints — see the .slide comment
     below), which is wider/taller than .work actually has room for once
     .window's height is fixed above. Without clipping that reserved-but-
     invisible overflow here, it would force a scrollbar despite nothing
     actually being cut off on screen. */
  overflow: hidden;
}
body.view-all .slidecap {
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase;
  color: var(--ink-3); margin-bottom: 8px; display: flex; justify-content: space-between;
}
body.view-all .slidecap b { color: var(--ink); font-weight: 400; }
body.view-all .slide {
  /* This card shows the one real page whose content actually exists in
     this document — not a preview thumbnail, the genuine content (see
     js/main.js's buildThumbs() comment on why every OTHER page's thumb
     is a sandboxed iframe instead). It used to be forced into a fixed
     16:9 box with overflow:hidden and clip real content; then briefly
     auto-height, which undersized short slides instead (in true
     Presentation mode a slide is always position:absolute;inset:0 — it
     always fills the full screen, with justify-content:safe center
     centering whatever content it holds — so a short slide never left a
     gap there the way auto-height did here).
     Fixed at 1600x900 — the same reference "design canvas" size the
     thumbnails already use — reproduces that: content centers inside a
     constant-size box instead of the box shrinking to the content. Both
     the width (this file) and .window's height (above) have to be fixed
     for js/main.js's scaleMainSlide() to correctly contain-fit that
     whole 1600x900 canvas into whatever space .work actually has,
     scaling both dimensions together via transform:scale so it fills
     the available window rather than sitting undersized inside it. */
  position: relative; inset: auto; opacity: 1; visibility: visible; transform: none; transition: none;
  aspect-ratio: auto; min-height: 0; height: 900px; width: 1600px; max-width: none; overflow: hidden;
  background: var(--porcelain);
  border: 1px solid var(--win-border); border-radius: 6px; box-shadow: 0 12px 30px -18px var(--shadow);
  padding: clamp(24px, 3.5vw, 56px) clamp(24px, 4vw, 64px); scroll-margin-top: calc(var(--top) + 24px);
}
body.view-all .slide.active { outline: 2px solid var(--azure); outline-offset: 3px; }
body.view-all .indexlink, body.view-all .ctl { display: none; }

.wrap { position: relative; z-index: 1; max-width: 1200px; width: 100%; margin: 0 auto; display: flex; flex-direction: column; gap: clamp(20px, 3.2vh, 36px); }
/* Every slide carries its own confined, independent network animation —
   a canvas child of the card itself rather than a window onto the
   page-level fixed canvas — so each one's backdrop looks composed for
   that specific card and doesn't shift as the page scrolls or the
   viewport resizes. Scoped to Slide view only: in Presentation mode
   every slide already shows the page-level fixed canvas (.slide there
   has no background of its own, and canvas#net is hidden above while
   Slide view is on), so turning this on there too would layer two
   independent animations on top of each other. */
.slide-net { display: none; }
body.view-all .slide-net {
  display: block; position: absolute; inset: 0; width: 100%; height: 100%;
  pointer-events: none; z-index: 0;
}
.k { font-family: var(--mono); font-size: 11.5px; letter-spacing: .1em; text-transform: uppercase; color: var(--kicker); display: flex; gap: 14px; align-items: center; }
.k b { color: var(--azure); font-weight: 400; }
h1, h2 { font-family: var(--display); margin: 0; text-wrap: balance; letter-spacing: -.03em; line-height: 1.02; font-weight: 300; }
h1 { font-size: clamp(38px, 6vw, 84px); letter-spacing: -.035em; }
h2 { font-size: clamp(28px, 3.8vw, 54px); }
h1 strong, h2 strong { font-weight: 600; color: var(--azure); }
.wordmark {
  /* Matches azure-legal-site's own cover wordmark: Playfair Display at
     600 (not the heavier default bold that face ships) rather than the
     page's usual Sora/Sans display face. */
  font-family: "Playfair Display", Georgia, serif;
  font-weight: 600;
}
.wordmark .chip {
  background: var(--azure); color: #fff; display: inline-block;
  padding: .04em .22em .08em; border-radius: 0; margin-right: .06em;
}
.wordmark .az { color: var(--azure); }
/* Dark ground: the chip becomes a hairline azure outline with azure type
   and "Legal" goes white — the solid chip is a flat block on ink. Looser
   than brand/brand.css's .wm-dark .chip (0.14em, equidistant) on purpose,
   for more breathing room at cover hero scale — but derived the same way:
   measured against Playfair Display's actual ink bounds for "Azure" (via
   canvas measureText — actualBoundingBox* metrics, not just CSS padding,
   since this font's line-height/ascent metrics don't map to visual gaps
   1:1) so the four sides read as even rather than guessed independently. */
html[data-theme="dark"] .wordmark .chip {
  background: transparent; color: var(--azure);
  box-shadow: inset 0 0 0 max(.012em, 1.5px) var(--azure);
  line-height: 1em; padding: 0 .335em .21em .33em; margin-right: .21em;
}
html[data-theme="dark"] .wordmark .az { color: var(--ink); }
h3 { font-family: var(--display); font-weight: 600; font-size: clamp(15px, 1.15vw, 18px); letter-spacing: -.01em; margin: 0 0 6px; }
/* Justified as the default: single-line labels (kickers, buttons, short
   card takeaways) are unaffected by justify — only the last line of a
   paragraph is exempt from stretching, and a one-line paragraph IS its
   last line. hyphens avoid the ragged word-gaps justify otherwise leaves
   in the deck's narrower columns (practice-area cards, table cells). */
p { margin: 0; text-align: justify; text-justify: inter-word; hyphens: auto; }
.lede { font-size: clamp(17px, 1.35vw, 21px); line-height: 1.5; color: var(--ink-2); max-width: 58ch; }
.fine { font-family: var(--mono); font-size: 11px; color: var(--ink-3); line-height: 1.6; max-width: 80ch; font-weight: 300; }
.row { display: flex; gap: 16px; align-items: center; flex-wrap: wrap; }
.head { display: grid; grid-template-columns: 1.1fr .9fr; gap: clamp(24px, 4vw, 64px); align-items: end; }
.head .lede { font-size: clamp(15px, 1.15vw, 18px); }
/* Zero-new-footprint backlink pattern: a short inline link tacked onto an
   existing lede's text flow rather than a new element/row (used on
   stack.html, whose slide is already height-tuned to one Presentation-
   mode screen). */
.inline-link { color: var(--azure); white-space: nowrap; margin-left: 4px; font-weight: 600; }
.card { background: var(--porcelain-2); border: 1px solid var(--porcelain-4); border-radius: 14px; padding: 20px 22px; display: flex; flex-direction: column; gap: 8px; transition: border-color .15s; }
.card:hover { border-color: var(--azure); }
.card .k { margin-bottom: 2px; }
.card p { font-size: clamp(14px, 1.05vw, 16px); line-height: 1.5; color: var(--ink-2); }

/* cover */
.cover .wrap { gap: clamp(24px, 4.5vh, 52px); }
/* Explicit grid rows rather than align-items:center on the whole block —
   the mark/divider span rows 1-3 (kicker through tagline — the entire
   lockup, now that copy and buttons are gone) via align-self:stretch, so
   the mark's height is exactly the lockup's height, not a guessed value. */
.cover-hero {
  display: grid; grid-template-columns: auto auto 1fr;
  column-gap: clamp(24px, 4vw, 56px); row-gap: clamp(16px, 2.8vh, 28px);
  align-items: start;
}
.cover-hero .cover-mark { grid-column: 1; grid-row: 1 / 4; align-self: stretch; height: auto; width: auto; }
.cover-hero .cover-divider { grid-column: 2; grid-row: 1 / 4; align-self: stretch; width: 1px; background: var(--porcelain-4); }
.cover-hero .cover-kicker { grid-column: 3; grid-row: 1; }
.cover-hero .wordmark { grid-column: 3; grid-row: 2; }
.cover-hero .cover-tagline { grid-column: 3; grid-row: 3; font-family: var(--display); color: var(--ink); }
.cover small { font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); line-height: 1.6; }

/* Cover mark loop: a continuous 6s cycle rather than a one-shot entrance
   — fade in (0-5%), both ribbons draw simultaneously in opposite
   directions (0-50%, so they finish together — see the path directions
   on .mark-main above), hold complete (50-90%), fade out (90-100%),
   repeat. The weave gaps aren't there from the start: each mask-cutter
   fades in right as the drawing sweep reaches that crossing. That's not
   a fixed guess — for this mark's geometry the crossing sits at 72.25%
   of the way along each revealing path (verified by calculation), which
   lands at 34-39% of the 6s loop; both crossings resolve to the same
   number because the mark is symmetric. Gated on .cover.active, added
   fresh by main.js's classList.toggle each time this slide becomes
   current, so the loop restarts clean on every visit, not just page
   load. Base state on every animated property is the correct *finished*
   look (solid strokes, gaps present, full opacity) so a visitor sees the
   real mark instantly if this rule fails to load for any reason. */
.cover-hero .cover-mark { opacity: 1; }
.cover-hero .cover-mark .mark-main path { stroke-dasharray: 280; stroke-dashoffset: 0; }
.cover-hero .cover-mark .mask-cutter { opacity: 1; }
.cover.active .cover-hero .cover-mark { animation: mark-cycle 6s linear infinite; }
.cover.active .cover-hero .cover-mark .mark-main path { animation: mark-draw 6s linear infinite; }
.cover.active .cover-hero .cover-mark .mask-cutter { animation: mark-cut 6s linear infinite; }
@keyframes mark-cycle { 0% { opacity: 0; } 5% { opacity: 1; } 90% { opacity: 1; } 100% { opacity: 0; } }
@keyframes mark-draw { 0% { stroke-dashoffset: 280; } 50% { stroke-dashoffset: 0; } 100% { stroke-dashoffset: 0; } }
@keyframes mark-cut { 0%, 34% { opacity: 0; } 39% { opacity: 1; } 100% { opacity: 1; } }
@media (prefers-reduced-motion: reduce) {
  .cover.active .cover-hero .cover-mark,
  .cover.active .cover-hero .cover-mark .mark-main path,
  .cover.active .cover-hero .cover-mark .mask-cutter { animation: none; }
}

/* Divider/kicker/wordmark/tagline rise in together, as one group, timed
   to the mark's *first* draw sweep (0-3s of the loop above) rather than
   waiting for it to finish and then popping in — they appear alongside
   the logo phasing in, not after it, and land together at the same
   moment the ribbons finish drawing. No stagger between them: same
   start, same duration, same finish. */
.cover-hero .cover-divider, .cover-hero .cover-kicker, .cover-hero .wordmark, .cover-hero .cover-tagline { opacity: 1; }
.cover.active .cover-hero .cover-divider,
.cover.active .cover-hero .cover-kicker,
.cover.active .cover-hero .wordmark,
.cover.active .cover-hero .cover-tagline { animation: cover-rise 3s cubic-bezier(.8, 0, 1, 1) both; }
/* Backloaded on purpose: opacity/position barely move until late in the
   3s span, then resolve quickly right as the mark's draw finishes — reads
   as "arriving together at the end" rather than fading in immediately
   and then just sitting there waiting out the rest of the duration. */
@keyframes cover-rise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }
@media (prefers-reduced-motion: reduce) {
  .cover.active .cover-hero .cover-divider, .cover.active .cover-hero .cover-kicker,
  .cover.active .cover-hero .wordmark, .cover.active .cover-hero .cover-tagline { animation: none; }
}

/* Hover / focus: a small brightness lift on both the header mark and the
   cover mark. The CSS filter here composes with the SVG's own internal
   drop-shadow filter (applied to .mark-main via the filter="" attribute)
   rather than replacing it — they're separate stages of the same
   rendering pipeline. */
.brand:hover .mark, .brand:focus-visible .mark { filter: brightness(1.18); }
.cover-hero:hover .cover-mark, .cover-hero:focus-within .cover-mark { filter: brightness(1.18); }

/* Pointer tilt — cover mark only (the header copy is ~30px, too small
   for a tilt to read). --tiltX/--tiltY are set by js/main.js on
   pointermove within .cover-hero and reset to 0 on pointerleave; the
   custom-property fallback of 0deg means this is inert until JS runs,
   and JS itself skips attaching the listener under reduced-motion. */
.cover-hero .cover-mark {
  transform: perspective(600px) rotateX(var(--tiltX, 0deg)) rotateY(var(--tiltY, 0deg));
  transition: transform .3s ease-out, filter .25s ease-out;
}
/* Thin rule under the tagline, matching the lockup reference — reuses
   --porcelain-4, which already remaps to a visible ink shade in dark
   theme, so no separate dark override is needed. */
.rule { height: 1px; width: 56px; background: var(--porcelain-4); }
.pills { display: flex; gap: 8px; flex-wrap: wrap; }
.pill { font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; border: 1px solid var(--porcelain-4); background: var(--glass); padding: 6px 12px; border-radius: 999px; color: var(--ink-2); }
.pill.az { border-color: var(--azure); color: var(--azure); }

/* problem */
/* align-items:center, not start: the headline runs to ~8 lines at this
   column width (measured: 441px) against the card grid's 324px. Trimming
   the headline copy doesn't close that — word-wrap at this width barely
   saves a line even for a noticeably shorter sentence — and narrowing
   the cards column to give the headline more room breaks the cards'
   own 2-line-per-card fit (measured: drops to 3 as soon as the column
   shrinks at all). Centering the shorter column against the taller one
   turns the mismatch into even whitespace above and below the cards,
   which reads as intentional instead of the cards trailing off short. */
.problem-grid { display: grid; grid-template-columns: .78fr 1.22fr; gap: clamp(20px, 3vw, 48px); align-items: center; }
.fails { display: flex; flex-direction: column; gap: 10px; }
/* Six short items read better as a compact 2-column grid than one long
   column of taller cards — same .fail cards, tighter padding, and sized
   (via .problem-grid's column split, below) so each wraps to two even
   lines rather than one cramped or three loose ones. min-height is a
   floor, not the driver — copy length is what's actually kept even. */
.fails.grid2 { display: grid; grid-template-columns: 1fr 1fr; gap: 10px 16px; align-items: stretch; }
.fails.grid2 .fail { padding: 13px 15px; min-height: 84px; align-items: center; }
.fail { background: var(--porcelain-2); border: 1px solid var(--porcelain-4); border-radius: 14px; padding: 16px 18px; display: grid; grid-template-columns: auto 1fr; gap: 16px; align-items: start; }
.fail .n { font-family: var(--mono); font-size: 11px; color: var(--azure); padding-top: 4px; letter-spacing: .06em; }
.fail h3 { margin: 0 0 4px; }
.fail p { font-size: clamp(14px, 1.05vw, 16px); line-height: 1.5; color: var(--ink-2); }
/* Matches the shared h2 size used everywhere else (54px max) — previously
   scaled down here because the longer headline pushed this slide past
   viewport height; #serve's own padding/gap tightening below now claws
   back that room instead, so the size doesn't have to be the fix. */
#serve .venn h2 { font-size: clamp(28px, 3.8vw, 54px); }

/* manifesto */
.mani { display: flex; flex-direction: column; border-top: 1px solid var(--porcelain-4); }
.mani > div { display: grid; grid-template-columns: 56px 1fr; gap: 20px; align-items: baseline; padding: clamp(12px, 2vh, 20px) 0; border-bottom: 1px solid var(--porcelain-4); }
.mani .n { font-family: var(--mono); font-size: 12px; color: var(--azure); }
.mani .l { font-family: var(--display); font-weight: 300; font-size: clamp(20px, 2.4vw, 34px); letter-spacing: -.025em; line-height: 1.15; text-wrap: balance; }
.mani .l strong { font-weight: 600; color: var(--azure); }
/* Compact variant: the arc slide runs .mani in a half-width column
   alongside .fails, so it needs the same list construction at a scale
   that doesn't overrun three stages plus an ongoing row. */
.mani.compact > div { padding: clamp(8px, 1.3vh, 12px) 0; }
.mani.compact .l { font-size: clamp(14px, 1.15vw, 17px); line-height: 1.28; }
/* What we believe: each belief is a <details> row rather than a plain
   div, so it can expand into its fuller explanation — same visual
   rhythm as the plain .mani rows (56px number column, border between
   rows), plus an expand indicator matching the .qa/.qa-drawer pattern
   used elsewhere in the deck. name="beliefs" on each <details> makes
   them a native mutually-exclusive accordion (one open at a time) so
   expanding six full explanations at once can't happen. */
.mani .belief { border-bottom: 1px solid var(--porcelain-4); }
.mani .belief summary {
  list-style: none; cursor: pointer; display: grid;
  grid-template-columns: 56px 1fr auto; gap: 20px; align-items: baseline;
  padding: clamp(9px, 1.6vh, 15px) 0;
}
.mani .belief summary::-webkit-details-marker { display: none; }
.mani .belief summary .pm { font-family: var(--mono); font-size: 14px; color: var(--ink-3); width: 14px; text-align: center; font-weight: 300; align-self: center; }
.mani .belief[open] summary .pm::before { content: "–"; }
.mani .belief:not([open]) summary .pm::before { content: "+"; }
/* Swap on hover rather than uniformly recolor: the line's plain (white)
   run goes azure and its already-azure <strong> clause goes white — the
   two colors trade places instead of the whole line changing as one. */
.mani .belief summary:hover .l, .mani .belief summary:hover .pm { color: var(--azure); }
.mani .belief summary:hover .l strong { color: var(--ink); }
.belief-body { padding: 0 0 clamp(12px, 2vh, 20px) 76px; }
.belief-body p { font-size: clamp(14.5px, 1.05vw, 16.5px); line-height: 1.6; color: var(--ink-2); margin: 0; }

/* who we serve (venn + lists) */
/* This slide is the tightest fit in the deck (a big diagram plus a full
   3-column legend), so it gets its own, smaller slide padding instead of
   the deck-wide 84px/foot+96px — that alone buys back real budget for
   the diagram without touching any other slide. Desktop only: the ID
   selector's specificity would otherwise beat the mobile media query's
   plain .slide rule below and undo its padding-top (tuned to clear the
   frosted progress-bar zone — see that rule's own comment). #fees gets
   the same treatment below — its table grew a lot once the lifecycle
   grouping was added and needed the same room back. */
@media (min-width: 961px) {
  #serve.slide { padding-top: 56px; padding-bottom: calc(var(--foot) + 56px); }
  /* padding-top has a hard floor here: .top header is position:fixed,
     64px tall, z-index above slide content — "safe center" only recenters
     when content fits comfortably; once a slide's content is tall enough
     to need the full viewport (this one, at short window heights), it
     falls back to flex-start and sits flush against padding-top, so that
     padding must clear the header on its own rather than relying on
     centering slack to do it. */
  #fees.slide { padding-top: 72px; padding-bottom: calc(var(--foot) + 10px); }
  /* .wrap's site-wide 1200px cap left real width unused on wider windows
     — this slide's two-column table benefits from it more than most
     (every extra pixel goes straight to "What is included"), so let it
     use the same edge-to-edge width (up to --pad) as the header bar. */
  #fees .wrap { max-width: none; }
  /* Was clamped down to near-zero to survive a very short test window,
     which left almost no air between the kicker/headline/lede/table/fine
     print — cramped even on normal-height screens, where there was
     plenty of room to spare. Scales with viewport height instead of a
     single fixed value, so it opens back up on taller windows and only
     tightens toward that old minimum on genuinely short ones. */
  #fees .wrap { gap: clamp(6px, 2.2vh, 22px); }
  #fees .fine { line-height: 1.4; }
  /* .lede's site-wide 58ch cap (tuned for a single centered column) was
     leaving most of the slide's width unused here and forcing a second
     wrapped line for no reason — let it use the full row like the
     headline already does, freeing a line of vertical space for the
     table below. */
  #fees .lede { max-width: none; }
  /* Same reasoning as #fees above: a 3x4 card grid plus search/filters/
     pager is the tallest content on the slide, so it gets the same
     scoped floor-clearing padding and full-width treatment rather than
     the default centered/1200px layout every lighter slide uses. */
  #insights.slide { padding-top: 64px; padding-bottom: calc(var(--foot) + 6px); }
  #insights .wrap { max-width: none; gap: clamp(4px, 1vh, 10px); }
  #insights .lede { max-width: none; }
  /* Same reasoning as #serve's venn h2: the shared 54px h2 size is tuned
     for slides with one headline and little else below it — here it was
     costing two full lines above a grid that already needs every pixel
     of vertical room it can get. */
  #insights h2 { font-size: clamp(22px, 2.6vw, 34px); }
}
.blog-controls { margin-bottom: 2px; }
.venn { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 4vw, 64px); align-items: center; }
/* The graphic's viewBox is now cropped tight to just the three lobes
   (background vignette/grid/noise/connector-node dressing removed per
   feedback that it read as a pasted-in rectangular image), so it carries
   far less dead space per pixel than the original full-canvas export.
   50vh measured out to overflowing the slide at ordinary window heights
   (confirmed: wrap taller than the available budget between the top
   padding and the footer clearance) — 46vh is the largest that still
   clears, now that #serve's own padding above have freed up enough room
   to afford it. See .slide's "safe center" note for what happens when a
   slide doesn't fit. */
.venn svg { width: 100%; height: auto; max-height: 46vh; grid-column: 2; grid-row: 1; transition: opacity .35s ease; }
/* Each lobe is clickable (opens its matching panel below) — brighten on
   hover/focus so that's discoverable before the first click, same idea
   as the digital asset layer buttons on the stack slide. Highlight is a
   JS-driven .hover class (nearest-lobe-by-distance, see js/main.js)
   rather than CSS :hover: the three circles overlap heavily, and
   whichever lobe paints last in the SVG wins every pixel of the overlap
   for hit-testing, so plain :hover only ever fired reliably in each
   lobe's own non-overlapped sliver — small, and different in size for
   each lobe depending on paint order. Distance-to-centre instead gives
   every lobe its full circle as a fair hover target. */
.venn svg .lobe { cursor: pointer; transition: filter .2s ease, opacity .2s ease; outline: none; }
.venn svg .lobe.hover, .venn svg .lobe:focus-visible { filter: brightness(1.3); }
.venn svg .lobe:active { filter: brightness(1.45); }
@media (prefers-reduced-motion: reduce) { .venn svg .lobe { transition: none; } }
/* Panel overlay: occupies the same grid cell as the diagram (explicit
   grid-column/row match, rather than a new row) and paints over it once
   a panel opens, while the diagram itself fades down under it — see
   js/main.js for the open/close wiring (click, Enter/Space, Escape,
   click-elsewhere-in-the-diagram-area all close it). */
.client-panels { grid-column: 2; grid-row: 1; position: relative; width: 100%; height: 100%; pointer-events: none; }
.venn.panel-open svg.client-fit { opacity: .16; }
.client-panel {
  position: absolute; inset: 0; margin: auto; max-width: 460px; max-height: 100%; overflow: auto;
  background: var(--porcelain-2); border: 1px solid var(--porcelain-4); border-radius: 16px;
  padding: 28px 30px; opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity .3s ease;
}
.client-panel.open { opacity: 1; visibility: visible; pointer-events: auto; }
.client-panel-title { font-family: var(--mono); font-size: 13px; letter-spacing: .16em; text-transform: uppercase; color: var(--azure); margin: 0 0 16px; }
.client-panel ol { list-style: none; margin: 0; padding: 0; counter-reset: panel; }
.client-panel li { counter-increment: panel; display: flex; gap: 14px; align-items: baseline; padding: 11px 0; border-top: 1px solid var(--porcelain-4); font-size: clamp(14px, 1.05vw, 16px); color: var(--ink); }
.client-panel li:last-child { border-bottom: 1px solid var(--porcelain-4); }
.client-panel li::before { content: counter(panel, decimal-leading-zero); font-family: var(--mono); font-size: 12px; color: var(--ink-3); flex: none; }
.client-panel-close {
  position: absolute; top: -6px; right: -6px; width: 34px; height: 34px; border-radius: 50%;
  border: 1px solid var(--porcelain-4); background: var(--porcelain-2); color: var(--ink-2);
  display: flex; align-items: center; justify-content: center; cursor: pointer;
  opacity: 0; visibility: hidden; transition: opacity .25s, border-color .2s; z-index: 2;
}
.venn.panel-open .client-panel-close { opacity: 1; visibility: visible; }
.client-panel-close:hover { border-color: var(--azure); color: var(--azure); }
.client-panel-close svg { width: 13px; height: 13px; }
@media (prefers-reduced-motion: reduce) { .venn svg, .client-panel, .client-panel-close { transition: none; } }
.legend { display: grid; grid-template-columns: repeat(3, 1fr); gap: 14px; }
.legend > div { display: flex; flex-direction: column; gap: 4px; padding-top: 8px; border-top: 2px solid var(--porcelain-4); }
.legend > div.az { border-top-color: var(--azure); }
.legend > div.ink { border-top-color: var(--ink); }
.legend > div.grey { border-top-color: var(--ink-3); }
.legend p { font-size: clamp(13px, .95vw, 15px); line-height: 1.32; color: var(--ink-2); }
.legend ul { margin: 2px 0 0; padding: 0; list-style: none; font-family: var(--mono); font-size: 11px; color: var(--ink-3); line-height: 1.55; }
.legend li::before { content: "— "; color: var(--azure); }

/* the right amount of lawyer: a stage-selector strip plus a 3-card
   spectrum with the middle (Azure Legal) card visually promoted — a
   deliberately different shape from the venn+legend pattern slide 04
   already uses, so consecutive slides don't read as the same template
   with new words dropped in. */
.stagebar { display: flex; flex-direction: column; gap: 10px; }
.stagebar-label { font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); margin: 0; }
.stages { display: grid; grid-template-columns: repeat(5, 1fr); gap: 8px; }
.stagepill {
  text-align: center; padding: 11px 8px; border-radius: 10px; border: 1px solid var(--porcelain-4);
  background: var(--porcelain-2); font-family: var(--mono); font-size: clamp(11px, .85vw, 12.5px);
  letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3);
}
.stagepill.on { border-color: var(--azure); color: var(--azure); background: var(--azure-tint); font-weight: 600; }
.stage-hint { display: flex; justify-content: space-between; gap: 10px; font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); letter-spacing: .03em; }
.stage-hint .mid { flex: 1; text-align: center; color: var(--azure); }

.spectrum { display: grid; grid-template-columns: 1fr 1.08fr 1fr; gap: 16px; align-items: stretch; }
.spec-card {
  position: relative; background: var(--porcelain-2); border: 1px solid var(--porcelain-4); border-radius: 14px;
  padding: 20px 22px; display: flex; flex-direction: column; gap: 8px;
}
.spec-card.featured {
  border: 2px solid var(--azure); box-shadow: 0 16px 36px -16px var(--azure-tint); transform: translateY(-6px);
}
.spec-k { font-family: var(--mono); font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); margin: 0; }
.spec-card.featured .spec-k { color: var(--azure); }
.spec-card h3 { font-size: clamp(15.5px, 1.2vw, 18.5px); margin: 0; }
.spec-card p { font-size: clamp(12.5px, .95vw, 14px); line-height: 1.45; color: var(--ink-2); margin: 0; }
.spec-take { margin-top: auto; padding-top: 8px; border-top: 1px solid var(--porcelain-4); font-size: clamp(12.5px, .95vw, 13.5px); color: var(--ink); font-weight: 600; }
.spec-card.featured .spec-take { color: var(--azure); border-top-color: var(--azure-tint); }

/* practice areas */
/* Two cards added (eight total) turned this into three rows instead of
   two — tighter card padding/gap and a smaller slide padding (scoped to
   this slide, desktop only, same pattern as #serve/#fees) claw back the
   room that adds up to across 8 cards instead of 6. */
.grid3 { display: grid; grid-template-columns: repeat(3, 1fr); gap: 8px; }
.grid3 .card { min-height: 0; padding: 11px 16px; gap: 4px; }
.grid3 .card p { line-height: 1.35; font-size: clamp(12.5px, .9vw, 14px); }
/* The 9th grid cell: two half-width buttons sharing one cell (not a
   10th card/row) — "Don't See Your Issue?" and "Related Articles" as
   two distinct, separately-bordered buttons side by side. */
.ask-pair { display: grid; grid-template-columns: 1fr 1fr; gap: 8px; }
.ask-btn {
  background: transparent; border: 1px dashed var(--porcelain-4); border-radius: 10px;
  display: flex; flex-direction: column; justify-content: center; gap: 3px;
  padding: 10px 12px; text-decoration: none; transition: border-color .15s;
}
.ask-btn:hover { border-color: var(--azure); }
.ask-btn h3 { color: var(--ink-2); font-weight: 500; font-size: clamp(12px, .88vw, 13.5px); margin: 0; line-height: 1.25; }
.ask-btn p { color: var(--ink-3); font-size: clamp(11px, .8vw, 12.5px); line-height: 1.3; margin: 0; }
.ask-btn:hover h3 { color: var(--azure); }
@media (min-width: 961px) {
  /* 10 cards (4 rows) since Related Articles became its own card rather
     than sharing the 9th cell — padding/gap tightened further here to
     claw back the room a 4th row adds, same reasoning as the earlier
     6->8-card pass this slide already went through once. */
  #practices.slide { padding-top: 28px; padding-bottom: calc(var(--foot) + 16px); }
  #practices .wrap { gap: 6px; }
}

/* digital-asset stack */
.stack-wrap { display: grid; grid-template-columns: 1fr 1.1fr; gap: clamp(24px, 3.5vw, 56px); align-items: center; }
@media (min-width: 961px) {
  /* The lede's added "Related articles" backlink pushed this slide's
     content past the default slide padding budget at shorter desktop
     viewport heights — tightened here the same way #fees/#practices/
     #serve already were. */
  #stack.slide { padding-top: 56px; padding-bottom: calc(var(--foot) + 40px); }
}
/* Four parallel scenarios, not a literal stack of layers anymore — full
   width instead of the old descending-staircase widths, which implied a
   hierarchy that no longer applies. */
.stack { display: flex; flex-direction: column; gap: 8px; align-items: stretch; }
.layer {
  border: 1px solid var(--porcelain-4); cursor: pointer; font: inherit; text-align: left;
  background: var(--porcelain-2); color: var(--ink); border-radius: 12px; padding: 14px 18px;
  display: grid; grid-template-columns: auto 1fr; gap: 16px; align-items: center;
  transition: background .15s, color .15s, border-color .15s, transform .15s;
}
.layer .n { font-family: var(--mono); font-size: 11px; color: var(--azure); letter-spacing: .06em; }
.layer .t { font-family: var(--display); font-weight: 600; font-size: clamp(14px, 1.15vw, 17px); letter-spacing: -.01em; }
.layer[aria-pressed="true"] { background: var(--azure); border-color: var(--azure); color: #fff; }
.layer[aria-pressed="true"] .n { color: rgba(255,255,255,.8); }
/* Border-only used to just be a thin, easy-to-miss cue that these rows
   are clickable — background tint plus a small nudge reads as tactile,
   not just a color change. */
.layer:hover:not([aria-pressed="true"]), .layer:focus-visible:not([aria-pressed="true"]) {
  border-color: var(--azure); background: var(--azure-tint); transform: translateX(4px);
}
@media (prefers-reduced-motion: reduce) { .layer { transition: background .15s, color .15s, border-color .15s; } .layer:hover:not([aria-pressed="true"]) { transform: none; } }
.detail { background: var(--porcelain-2); border: 1px solid var(--porcelain-4); border-radius: 14px; padding: 24px 26px; display: flex; flex-direction: column; gap: 10px; min-height: 250px; }
.detail h3 { font-size: clamp(18px, 1.6vw, 24px); margin: 0; letter-spacing: -.02em; line-height: 1.2; }
.detail p { font-size: clamp(14.5px, 1.1vw, 17px); line-height: 1.5; color: var(--ink-2); }
/* Single column, not the old 2-up grid: with four items of uneven
   length, pairing them into a 2x2 grid produced ragged row heights
   (whichever item wrapped to an extra line stretched its row-mate's gap
   for no reason). A plain stacked list reads evenly regardless of how
   any one line wraps. Bulleted with a small dot instead of the flat
   dash-tick, which read more like a stray hyphen than a bullet. */
.detail ul { margin: 4px 0 0; padding: 0; list-style: none; display: flex; flex-direction: column; gap: 8px; font-size: clamp(13.5px, 1vw, 15.5px); color: var(--ink-2); line-height: 1.45; }
.detail li { padding-left: 16px; position: relative; }
.detail li::before { content: ""; position: absolute; left: 1px; top: .55em; width: 5px; height: 5px; border-radius: 50%; background: var(--azure); }

/* fees table */
/* The lifecycle stage (Formation/Financing/Scaling/Exit) is its own
   leading column rather than a spanning group-header row (tried first,
   reverted — it broke down once a stage's rows weren't contiguous-
   looking as a group). Smaller type and tighter row padding still apply
   throughout, since the table is the tallest content on the slide — see
   #fees's own padding/gap overrides further down for the rest of that
   fix. */
/* table-layout:fixed + explicit % widths: split into two ~half-width
   columns, the nowrap Stage/Matter/Typical-time/Fee cells were claiming
   their full intrinsic content width regardless of how little room the
   table actually had (default auto layout sizes non-wrapping columns
   first, whatever's left goes to the one column that can shrink), which
   crushed "What is included" down to a sliver and produced 5-7 wrapped
   lines a row. Fixed widths force every column to respect its share;
   Stage/Matter now wrap within theirs instead of dictating it. */
table { width: 100%; border-collapse: collapse; table-layout: fixed; font-size: clamp(11.5px, .8vw, 13px); }
/* Fixed px, not %: Stage ("CYBERSECURITY") and Fee ("from $15,000") are
   nowrap and have a real minimum width regardless of how narrow the
   table gets — a % share shrinks below that minimum and the content
   overflows/clips. "What is included" is the one column left with no
   declared width, so table-layout:fixed hands it whatever's left. */
/* 90px, not 78 — "Commercial" nearly filled the old 78px column, leaving
   only ~4px before the Matter text started (vs ~11px for shorter Stage
   words like "Formation"), which read as the two columns running
   together rather than sitting apart like every other row. */
th:nth-child(1), td:nth-child(1) { width: 90px; }
th:nth-child(2), td:nth-child(2) { width: 104px; }
th:nth-child(4), td:nth-child(4) { width: 76px; }
th:nth-child(5), td:nth-child(5) { width: 68px; }
th { font-family: var(--mono); font-weight: 400; font-size: 10.5px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); text-align: left; padding: 0 12px 4px 0; border-bottom: 1px solid var(--ink); }
/* "from" used to repeat beside every figure ("from $2,500", "from
   $3,500"...) — since every fee in this table is a starting price, that
   word now lives once in the header ("Fee (from)") instead of twelve
   times in the body, freeing width for "What is included" and letting
   every dollar sign start flush left with no offset to account for. */
th .unit-note { font-weight: 300; text-transform: none; letter-spacing: 0; color: var(--ink-3); opacity: .8; }
th:last-child, td.amt { text-align: left; }
td { padding: 6px 12px 6px 0; border-bottom: 1px solid var(--porcelain-4); vertical-align: top; color: var(--ink-2); line-height: 1.2; }
/* The header's border-bottom and the first row's own td padding-top (1px)
   left almost no air between the rule and "Company formation" — a bit of
   breathing room just on that first row, not a change to row spacing
   throughout the rest of the table. */
tbody tr:first-child td { padding-top: 6px; }
/* Named .stagecol, not .stage — .stage is already the main presentation
   viewport's own class (100vh), and reusing it here silently gave every
   row a 900px-tall cell (this exact collision happened once already
   this session with the stage-selector pills on slide 05, since renamed
   to .stagepill for the same reason). */
td.stagecol { font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase; color: var(--azure); }
td:nth-child(2) { font-family: var(--display); font-weight: 600; color: var(--ink); }
td.amt { font-family: var(--mono); font-variant-numeric: tabular-nums; white-space: nowrap; color: var(--ink); }
td.amt small { color: var(--ink-3); font-size: 11px; }
/* A trailing qualifier ("per month") sits on its own line under the
   figure rather than beside it — inline, it read as part of the number
   itself ("$2,500 per month" as one run) rather than a unit label on a
   fixed fee. */
td.amt small.unit { display: block; margin-top: 2px; }
td:nth-child(2) small { font-family: var(--body); font-weight: 400; font-size: 11px; color: var(--ink-3); margin-left: 4px; }
tr:hover td { background: var(--row-hover); }
/* Token launch program's phased breakdown: collapsed by default (each
   .sub row starts [hidden]) and toggled open by clicking the runway row
   itself — see js/main.js. Indented a step further per stage (Stage 1
   slightest, Stage 4 deepest) so the sequence reads as a staircase, at
   the same type size as every other row (a smaller size read as an
   unrelated footnote rather than four real line items). No border
   between consecutive stages keeps the open group visually joined. */
tr.runway-toggle { cursor: pointer; }
tr.runway-toggle .pm { display: inline-block; width: 12px; color: var(--azure); font-family: var(--mono); font-weight: 400; }
tr.runway-toggle .pm::before { content: "+"; }
tr.runway-toggle[aria-expanded="true"] .pm::before { content: "\2212"; }
tr.sub td { padding-top: 2px; padding-bottom: 2px; border-bottom: none; color: var(--ink-3); }
tr.sub td:nth-child(2) { font-family: var(--body); font-weight: 400; color: var(--ink-2); white-space: normal; }
.stage-n { font-family: var(--mono); font-size: 11px; letter-spacing: .04em; color: var(--azure); margin-right: 8px; }
tr.sub.sub-1 td:nth-child(2) { padding-left: 4px; }
tr.sub.sub-2 td:nth-child(2) { padding-left: 28px; }
tr.sub.sub-3 td:nth-child(2) { padding-left: 52px; }
tr.sub.sub-4 td:nth-child(2) { padding-left: 76px; }
/* Bundle total sits at the same single indent level as the stages
   (not a further staircase step — it's a sum across all four, not a
   fifth stage) with a top border to separate it from the stages above
   and a bottom border to close the group before the next real matter. */
tr.sub-total td:nth-child(2) { padding-left: 14px; }
tr.sub-total td { border-top: 1px solid var(--porcelain-4); border-bottom: 1px solid var(--porcelain-4); padding-top: 4px; padding-bottom: 4px; }
tr.sub-total td:nth-child(2), tr.sub-total .amt { color: var(--ink); font-weight: 600; }
/* Two tables side by side instead of one stacked list — lifecycle-stage
   packages (Formation/Financing/Scaling) on the left, the newer
   category-priced packages (Commercial/Marketing/Employment/Cybersecurity/
   Privacy) on the right. Each column runs its own <table> with its own
   header row rather than sharing one wide table, so column widths (and
   the "What is included" wrap point) are tuned to each side's own content
   instead of one compromise width for both. */
.fees-columns { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(20px, 3vw, 40px); align-items: start; }
.tablewrap { overflow-x: auto; }

/* first-call questions */
.qa { display: grid; grid-template-columns: 1fr 1fr; gap: 0 clamp(24px, 4vw, 64px); border-top: 1px solid var(--ink); }
.qa details { border-bottom: 1px solid var(--porcelain-4); }
.qa summary {
  list-style: none; cursor: pointer; display: grid; grid-template-columns: 1fr auto; gap: 16px;
  align-items: baseline; padding: 14px 0; font-family: var(--display); font-weight: 600;
  font-size: clamp(14.5px, 1.15vw, 17px); letter-spacing: -.01em; line-height: 1.3;
}
.qa summary::-webkit-details-marker { display: none; }
.qa summary .pm { font-family: var(--mono); font-size: 14px; color: var(--ink-3); width: 14px; text-align: center; font-weight: 300; }
.qa details[open] .pm::before { content: "–"; }
.qa details:not([open]) .pm::before { content: "+"; }
.qa summary:hover { color: var(--azure); }
.qa .a { padding: 0 0 14px; font-size: clamp(14px, 1.05vw, 16px); line-height: 1.55; color: var(--ink-2); max-width: 60ch; }

/* first-call questions, collapsed into a single expandable menu on Book a call */
.qa-drawer { border-top: 1px solid var(--porcelain-4); }
.qa-drawer > summary {
  list-style: none; cursor: pointer; display: flex; align-items: baseline; gap: 12px;
  padding: 12px 0; font-family: var(--mono); font-size: 11.5px; letter-spacing: .1em;
  text-transform: uppercase; color: var(--ink-3);
}
.qa-drawer > summary::-webkit-details-marker { display: none; }
.qa-drawer > summary .pm { font-family: var(--mono); font-size: 13px; color: var(--azure); width: 12px; text-align: center; font-weight: 300; }
.qa-drawer[open] > summary .pm::before { content: "–"; }
.qa-drawer:not([open]) > summary .pm::before { content: "+"; }
.qa-drawer > summary:hover { color: var(--azure); }
.qa-drawer .qa { grid-template-columns: 1fr 1fr; margin-top: 4px; }

/* team */
.team-grid { display: grid; grid-template-columns: 1.3fr .9fr; gap: clamp(24px, 4vw, 64px); align-items: stretch; }
.team-creds { display: grid; grid-template-columns: 1fr 1fr; gap: 20px; border-top: 1px solid var(--porcelain-4); padding-top: 10px; margin-top: 0; }
.team-creds .k { margin-bottom: 6px; }
.team-creds ul { margin: 0; padding: 0; list-style: none; font-size: clamp(13px, 1vw, 15px); color: var(--ink-2); line-height: 1.6; }
.team-creds li::before { content: "— "; color: var(--azure); }
.team-quote { background: var(--porcelain-2); border: 1px solid var(--porcelain-4); border-radius: 14px; padding: 10px 20px; display: flex; flex-direction: column; gap: 6px; }
.team-quote p { font-family: var(--display); font-weight: 300; font-size: clamp(17px, 1.5vw, 21px); letter-spacing: -.02em; line-height: 1.3; color: var(--ink); margin: 0; text-wrap: balance; }
.team-quote cite { font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); font-style: normal; letter-spacing: .04em; }
/* Jonathan's own accounts — distinct from the firm's @azurelegal socials
   in the sitewide footer. Same icon-row language as .foot-social. */
.team-socials { display: flex; align-items: center; gap: 14px; }
.team-socials a { color: var(--ink-3); display: flex; transition: color .15s; }
.team-socials a:hover { color: var(--azure); }
.team-socials svg { width: 17px; height: 17px; }
/* Photo box: a placeholder standing in for a real headshot (none supplied
   yet) — same glass-card language as .team-quote/.card so it reads as part
   of this deck's system rather than a bare image well. Matches the left
   column's height (align-items: stretch) rather than a fixed aspect ratio,
   so it can never be the thing that pushes this slide past its vertical
   budget — a real photo, once supplied, can reintroduce a ratio via
   object-fit without this box's sizing changing. */
.team-photo {
  min-height: 260px; border-radius: 14px; background: var(--porcelain-2);
  border: 1px solid var(--porcelain-4); display: flex; flex-direction: column;
  align-items: center; justify-content: center; gap: 12px; color: var(--ink-3);
}
.team-photo-mark { width: clamp(48px, 6vw, 68px); height: clamp(48px, 6vw, 68px); color: var(--azure); opacity: .55; }
.team-photo-label { font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--ink-3); margin: 0; }
/* The quote moved into the left column (below credentials) pushed that
   column taller than the slide's default vertical budget — same fix as
   #serve/#fees/#practices: claw back the generous default slide padding,
   desktop only, rather than shrinking type to fit. */
@media (min-width: 961px) {
  #team.slide { padding-top: 32px; padding-bottom: calc(var(--foot) + 24px); }
}
/* .lede's site-wide 58ch cap (tuned for a single centered column) reads as
   an accidental narrow gutter here, where the paragraphs share a row with
   the photo box — let them fill the actual column width instead. */
#team .lede { max-width: none; }

/* insights / blog */
.blog-controls { display: flex; align-items: center; justify-content: space-between; gap: 20px; flex-wrap: wrap; }
.blog-search {
  display: flex; align-items: center; gap: 8px; border: 1px solid var(--porcelain-4);
  border-radius: 10px; padding: 8px 12px; background: var(--porcelain-2); flex: 0 1 260px;
}
.blog-search svg { width: 15px; height: 15px; color: var(--ink-3); flex-shrink: 0; }
.blog-search input {
  border: 0; background: transparent; font-family: var(--body); font-size: 13.5px;
  color: var(--ink); outline: none; width: 100%;
}
.blog-search input::placeholder { color: var(--ink-3); }
/* Text tabs, not pills — an underline on the active one is enough of a
   cue without a filled/rounded button shape for every category. */
.blog-filters { display: flex; gap: 18px; flex-wrap: wrap; }
.blog-filter {
  background: transparent; border: 0; border-bottom: 2px solid transparent; cursor: pointer;
  font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); padding: 2px 0 6px; transition: color .15s, border-color .15s;
}
.blog-filter:hover { color: var(--ink); }
.blog-filter.is-active { color: var(--azure); border-bottom-color: var(--azure); }

.blog-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
/* Same border language as every other card in the deck (.card/.fail/
   .note): 14px radius, generous padding — the earlier compressed 10px
   radius/6px padding version was the one thing on this slide that didn't
   match the rest of the deck's card system. Taller and narrower than
   before per feedback: bigger padding front-loads height, and a
   min-height (rather than letting content alone decide) is what actually
   makes the box read as taller instead of just less cramped. */
.post-card {
  background: var(--porcelain-2); border: 1px solid var(--porcelain-4); border-radius: 14px;
  padding: 12px 16px; display: flex; flex-direction: column; gap: 6px; min-height: 142px;
  transition: border-color .15s;
}
.post-card:hover { border-color: var(--azure); }
.post-meta { display: flex; justify-content: space-between; gap: 8px; font-family: var(--mono); font-size: 10px; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-3); }
.post-meta .post-cat { color: var(--azure); }
.post-card h3 { font-size: clamp(14px, 1vw, 15.5px); font-weight: 600; letter-spacing: -.01em; line-height: 1.25; margin: 0; }
/* Hard 2-line clamp, not just a smaller font — without it, a slightly
   longer excerpt makes its whole grid row (and every card sharing it)
   taller than the rest, so row heights stop matching across the grid. */
.post-card p { font-size: clamp(12px, .9vw, 13px); line-height: 1.4; color: var(--ink-2); margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.post-foot {
  margin-top: auto; display: flex; justify-content: space-between; gap: 8px;
  font-family: var(--mono); font-size: 10px; color: var(--ink-3); padding-top: 8px; border-top: 1px solid var(--porcelain-4);
}
.blog-empty { grid-column: 1 / -1; color: var(--ink-3); font-size: 14px; padding: 20px 0; }
.blog-pager { display: flex; align-items: center; justify-content: center; gap: 14px; font-family: var(--mono); font-size: 11px; color: var(--ink-3); }
.blog-pager button {
  width: 30px; height: 30px; border-radius: 50%; border: 1px solid var(--porcelain-4); background: var(--porcelain-2);
  color: var(--ink); cursor: pointer; display: flex; align-items: center; justify-content: center; transition: border-color .15s, opacity .15s;
}
.blog-pager button:hover:not(:disabled) { border-color: var(--azure); }
.blog-pager button:disabled { opacity: .35; cursor: default; }
.blog-pager button svg { width: 12px; height: 12px; }

/* book */
@media (min-width: 961px) {
  /* The intake form grew a topic select, a honeypot, two consent
     checkboxes, and a reCAPTCHA note this session — tightened here the
     same way #fees/#practices/#serve/#stack already were, so the longer
     form still fits the default slide padding budget. */
  #book.slide { padding-top: 56px; padding-bottom: calc(var(--foot) + 40px); }
}
.book { display: grid; grid-template-columns: 1fr 1fr; gap: clamp(24px, 4vw, 64px); align-items: start; }
.slots { display: flex; flex-direction: column; gap: 10px; }
.dayrow { display: grid; grid-template-columns: 120px 1fr; gap: 12px; align-items: center; }
.dayrow .dl { font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); }
.dayrow .dl b { display: block; color: var(--ink); font-weight: 400; font-size: 13px; }
.times { display: flex; gap: 8px; flex-wrap: wrap; }
.slot { font-family: var(--mono); font-size: 12px; padding: 9px 13px; border-radius: 999px; border: 1px solid var(--porcelain-4); background: var(--porcelain-2); color: var(--ink); cursor: pointer; }
.slot:hover { border-color: var(--azure); }
.slot[aria-pressed="true"] { background: var(--azure); border-color: var(--azure); color: #fff; }
form { display: flex; flex-direction: column; gap: 8px; }
.field { display: flex; flex-direction: column; gap: 4px; }
.field label { font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); }
.field input, .field textarea { background: var(--porcelain-2); border: 1px solid var(--porcelain-4); padding: 9px 12px; font-family: var(--body); font-size: 15px; color: var(--ink); border-radius: 10px; width: 100%; }
.field input:focus, .field textarea:focus { outline: 0; border-color: var(--azure); box-shadow: 0 0 0 3px var(--azure-tint); }
.field textarea { min-height: 56px; resize: vertical; }
.field select { background: var(--porcelain-2); border: 1px solid var(--porcelain-4); padding: 9px 12px; font-family: var(--body); font-size: 15px; color: var(--ink); border-radius: 10px; width: 100%; }
.field select:focus { outline: 0; border-color: var(--azure); box-shadow: 0 0 0 3px var(--azure-tint); }
.field-optional { text-transform: none; letter-spacing: 0; color: var(--ink-3); font-weight: 400; }
/* honeypot: visually and functionally hidden from real visitors, still
   present in the DOM/tab order-adjacent enough for naive bots to fill in
   (see intake-create.js's server-side check). */
.intake-honeypot { position: absolute; width: 1px; height: 1px; overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; }
.intake-consent { display: flex; gap: 10px; align-items: flex-start; }
.intake-consent input { margin-top: 3px; }
.intake-consent label { font-size: 12.5px; color: var(--ink-2); line-height: 1.5; }
.intake-recaptcha-note { font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); line-height: 1.5; }
.intake-recaptcha-note a { color: var(--ink-3); text-decoration: underline; }
.toast { font-family: var(--mono); font-size: 11.5px; color: var(--azure); min-height: 18px; }
.legal { font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); line-height: 1.6; display: flex; gap: 16px; flex-wrap: wrap; border-top: 1px solid var(--porcelain-4); padding-top: 12px; }

/* ---------- footer ---------- */
/* Persistent, global, frosted like .top — it sits fixed above content
   that scrolls beneath it in Slide view and on mobile, same reasoning
   as .top's own comment above. Policies and socials need to be reachable
   from every slide, not just the last one, so this lives outside .stage
   rather than inside the Book a call slide. */
.foot {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 30; height: var(--foot);
  display: flex; align-items: center; justify-content: space-between; gap: 20px;
  padding: 0 var(--pad); background: var(--chrome);
  -webkit-backdrop-filter: blur(10px); backdrop-filter: blur(10px);
  border-top: 1px solid var(--porcelain-4); pointer-events: none;
  font-family: var(--mono); font-size: 10.5px; letter-spacing: .03em; color: var(--ink-3);
}
.foot > * { pointer-events: auto; }
.foot-l { display: flex; align-items: center; gap: 14px; min-width: 0; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.foot-l .adv { color: var(--ink-3); overflow: hidden; text-overflow: ellipsis; }
.foot-links { display: flex; gap: 16px; white-space: nowrap; }
.foot-links a:hover { color: var(--azure); }
.foot-social { display: flex; align-items: center; gap: 14px; }
.foot-social a { color: var(--ink-3); display: flex; transition: color .15s; }
.foot-social a:hover { color: var(--azure); }
.foot-social svg { width: 15px; height: 15px; }

/* ---------- standalone legal pages (terms / privacy / refund) ---------- */
/* These pages read top-to-bottom rather than living inside the deck's
   fixed-viewport slide mechanics, so the global overflow:hidden lock
   (see the .slide comment above) is lifted for them specifically via
   the html.page class set on <html> in each page. */
html.page, html.page body { height: auto; overflow: auto; }
.legal-page { max-width: 760px; margin: 0 auto; padding: calc(var(--top) + 56px) var(--pad) calc(var(--foot) + 64px); display: flex; flex-direction: column; gap: 28px; }
.legal-page h1 { font-size: clamp(32px, 5vw, 52px); margin: 4px 0 0; }
.legal-updated { font-family: var(--mono); font-size: 11.5px; color: var(--ink-3); letter-spacing: .05em; margin: 0; }
.legal-section { display: flex; flex-direction: column; gap: 10px; border-top: 1px solid var(--porcelain-4); padding-top: 24px; }
.legal-section h2 { font-family: var(--display); font-weight: 600; font-size: clamp(18px, 1.6vw, 22px); letter-spacing: -.01em; margin: 0; }
/* 404 page */
.not-found-page h1 { font-size: clamp(28px, 4.5vw, 44px); }
.not-found-links { display: grid; grid-template-columns: 1fr 1fr; gap: 14px; margin-top: 8px; }
@media (max-width: 640px) { .not-found-links { grid-template-columns: 1fr; } }
.legal-section p, .legal-section li { font-size: clamp(14.5px, 1.05vw, 16px); line-height: 1.6; color: var(--ink-2); }
.legal-section a { color: var(--azure); text-decoration: underline; text-underline-offset: 2px; }
.legal-section ul { margin: 4px 0 0; padding-left: 20px; }
.legal-section li { margin-bottom: 4px; }

/* ---------- responsive ---------- */
@media (max-width: 960px) {
  /* .foot wraps onto two rows here (see below), so the clearance every
     other rule derives from --foot needs to grow with it. */
  :root { --foot: 68px; }
  body { overflow: auto; }
  .stage { height: auto; }
  /* padding-top must clear .segs::before's frosted zone (top: var(--top) - 10px,
     height 34px, so it ends at var(--top) + 24px) or a slide's first line
     renders partly behind that backdrop-blur layer as it scrolls under it. */
  .slide { position: static; opacity: 1; visibility: visible; transform: none; padding: calc(var(--top) + 36px) 20px calc(var(--foot) + 40px); border-bottom: 1px solid var(--porcelain-4); overflow: visible; }
  .ctl, .indexlink, .toggle { display: none; }
  .foot { flex-wrap: wrap; justify-content: center; gap: 6px 18px; height: auto; padding: 10px 16px; text-align: center; }
  .foot-l .adv { display: none; }
  .segs { left: 16px; right: 16px; }
  .segs a span { display: none; }
  .overlay ol { grid-template-columns: 1fr; }
  .overlay li a { grid-template-columns: 36px 1fr; white-space: normal; }
  .venn, .book, .dayrow, .problem-grid, .head, .stack-wrap, .qa, .team-grid, .fails.grid2, .spectrum { grid-template-columns: 1fr; }
  /* Single column here, so the overlay trick (panel painted directly on
     top of the diagram, same grid cell) has no diagram-sized cell to sit
     in — stack it as a normal block below the diagram instead. Both
     grid-column AND grid-row need resetting to auto: the desktop rule
     pins the svg to column 2/row 1 explicitly for the overlay, and
     leaving row:1 in place here made the grid auto-place the (otherwise
     unpositioned) text block into row 2 instead — text ended up
     rendering after the diagram rather than before it. */
  .venn svg, .client-panels { grid-column: 1; grid-row: auto; }
  .client-panels { position: static; height: auto; margin-top: 16px; }
  .client-panel { position: static; max-width: none; margin: 0 0 12px; }
  .client-panel-close { top: -10px; right: 0; }
  .stages { grid-template-columns: repeat(3, 1fr); }
  .stage-hint { flex-direction: column; gap: 2px; text-align: center; }
  .spec-card.featured { transform: none; }
  .team-creds { grid-template-columns: 1fr; gap: 12px; }
  /* The explicit grid-row placements above (mark/divider pinned to the
     wordmark's row) don't collapse cleanly to one column — switching to
     flex instead makes DOM order (mark, divider, kicker, wordmark,
     tagline, lede, buttons) the stacking order directly. */
  .cover-hero { display: flex; flex-direction: column; align-items: flex-start; gap: 20px; }
  /* Must match .cover-hero .cover-mark's specificity (two classes) from
     the unconditional desktop rule above — a bare .cover-mark here loses
     the cascade to that rule's height:auto/width:auto regardless of this
     media query, leaving the mark unconstrained (it was rendering huge:
     no grid to stretch it against anymore once .cover-hero switches to
     flex here, but height:auto was still winning over this height:100px). */
  .cover-hero .cover-mark { height: 100px; width: auto; }
  .cover-divider { display: none; }
  .legend, .grid3, .blog-grid, .fees-columns { grid-template-columns: 1fr; }
  .blog-controls { flex-direction: column; align-items: stretch; }
  .blog-search { flex: 1 1 auto; }
  .layer { width: 100% !important; }
  .detail { min-height: 0; }
  .mani > div { grid-template-columns: 36px 1fr; }
  canvas#net { opacity: .5 !important; }
  .cover .btn.lg { width: 100%; justify-content: center; }
  body.view-all { overflow: auto; }
  body.view-all .window { grid-template-columns: 1fr; border-radius: 0; border: 0; box-shadow: none; background: transparent; }
  body.view-all .thumbs, body.view-all .titlebar { display: none; }
  /* Not plain 0: .top is a fixed header and .foot a fixed footer at every
     width, so .stage still needs top/bottom clearance to keep .window
     from rendering underneath them — zeroing it out entirely (as before)
     put .window's top edge at viewport y:0, directly behind the header. */
  body.view-all .stage { padding: calc(var(--top) + 12px) 0 var(--foot); }
  body.view-all .work { padding: 12px; }
  body.view-all .slide { aspect-ratio: auto; }
}

/* ==========================================================================
   Blog + admin CMS
   Backs insights.html's teaser grid (deck-styled) and the server-rendered
   /blog archive + post pages and /admin* CMS (plain html.page-style
   pages, same template as terms.html — see _page-template.js). One
   shared block since both surfaces reuse the same card/form language as
   the rest of the site.
   ========================================================================== */

/* insight-* : used by both insights.html's client-rendered teaser and the
   server-rendered /blog archive/post pages (blog-archive.js's cardHtml),
   so both look like the same product. */
.insight-filters { display: flex; gap: 18px; flex-wrap: wrap; margin-bottom: 4px; }
.insight-filter {
  background: transparent; border: 0; border-bottom: 2px solid transparent; cursor: pointer;
  font-family: var(--mono); font-size: 11px; letter-spacing: .06em; text-transform: uppercase;
  color: var(--ink-3); padding: 2px 0 6px; transition: color .15s, border-color .15s;
  text-decoration: none; display: inline-block;
}
.insight-filter:hover { color: var(--ink); }
.insight-filter.is-active { color: var(--azure); border-bottom-color: var(--azure); }
.insight-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 8px; }
.insight-card {
  background: var(--porcelain-2); border: 1px solid var(--porcelain-4); border-radius: 14px;
  padding: 12px 16px; display: flex; flex-direction: column; gap: 6px; min-height: 142px;
  transition: border-color .15s;
}
.insight-card:hover { border-color: var(--azure); }
.insight-card.is-hidden { display: none; }
.insight-title { font-size: clamp(14px, 1vw, 15.5px); font-weight: 600; letter-spacing: -.01em; line-height: 1.25; margin: 0; }
.insight-title a { color: inherit; text-decoration: none; }
.insight-title a:hover { color: var(--azure); }
.insight-author { font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); margin: 0; }
.insight-meta { display: flex; justify-content: space-between; gap: 8px; font-family: var(--mono); font-size: 10px; letter-spacing: .05em; text-transform: uppercase; color: var(--ink-3); }
.insight-tag { color: var(--azure); overflow: hidden; text-overflow: ellipsis; white-space: nowrap; max-width: 65%; }
.insight-tag a { color: inherit; text-decoration: none; }
.insight-teaser { font-size: clamp(12px, .9vw, 13px); line-height: 1.4; color: var(--ink-2); margin: 0; display: -webkit-box; -webkit-line-clamp: 2; -webkit-box-orient: vertical; overflow: hidden; }
.insight-foot { margin-top: auto; display: flex; justify-content: space-between; align-items: center; gap: 8px; font-family: var(--mono); font-size: 10px; color: var(--ink-3); padding-top: 8px; border-top: 1px solid var(--porcelain-4); }
.insight-read { color: var(--azure); text-decoration: none; font-weight: 600; display: inline-flex; align-items: center; gap: 4px; }
.insight-read:hover { text-decoration: underline; }
.insight-read svg { width: 12px; height: 12px; }
.insight-viewall { display: inline-block; margin-top: 10px; }
.blog-empty { grid-column: 1 / -1; color: var(--ink-3); font-size: 14px; padding: 20px 0; }

@media (max-width: 960px) {
  .insight-grid { grid-template-columns: 1fr; }
}

/* blog-* : the standalone, plain (html.page-style) /blog archive + post
   pages, rendered by netlify/functions/_page-template.js. .blog-page is
   applied to <main> alongside the existing .legal-page (same max-width/
   padding rhythm as terms.html), these rules layer blog-specific type and
   layout on top of it. */
/* .legal-page's default 760px max-width fits a single narrow column of
   prose (terms.html etc.); the blog/admin pages need more room for a
   sidebar-flanked article or a data table, so widen it here specifically. */
.legal-page.blog-page, .legal-page.admin-shell { max-width: 1200px; }
.blog-page .blog-eyebrow { font-family: var(--mono); font-size: 11px; letter-spacing: .1em; text-transform: uppercase; color: var(--azure); margin: 0 0 6px; }
.blog-h1 { font-family: var(--display); font-size: clamp(28px, 4vw, 44px); font-weight: 600; letter-spacing: -.01em; line-height: 1.1; margin: 0 0 10px; }
.blog-lede { font-size: clamp(14.5px, 1.1vw, 17px); color: var(--ink-2); max-width: 62ch; margin: 0 0 22px; }
.blog-container { max-width: 1080px; }
.blog-container .insight-grid { margin: 20px 0 28px; }
.blog-container .insight-filters { margin-bottom: 18px; }
.blog-pagination { display: flex; align-items: center; justify-content: center; gap: 18px; font-family: var(--mono); font-size: 12px; color: var(--ink-3); margin-top: 24px; }
.blog-pagination a { color: var(--azure); text-decoration: none; font-weight: 600; }
.blog-pagination a:hover { text-decoration: underline; }

/* article page: two side columns (related/recent) flanking the article */
.blog-post-layout { display: grid; grid-template-columns: 220px minmax(0, 1fr) 220px; gap: 40px; align-items: start; max-width: 1200px; }
.blog-container-article { min-width: 0; }
.blog-byline { font-family: var(--mono); font-size: 12px; color: var(--ink-3); margin: 0 0 4px; }
.blog-byline a { color: var(--azure); }
.blog-post-meta { display: flex; gap: 14px; font-family: var(--mono); font-size: 11px; color: var(--ink-3); margin: 0 0 10px; }
.blog-tags { font-family: var(--mono); font-size: 11px; color: var(--ink-3); margin: 0 0 18px; }
.blog-tags a { color: var(--azure); text-decoration: none; }
.blog-share-bar { display: flex; align-items: center; justify-content: space-between; gap: 12px; padding: 12px 0; border-top: 1px solid var(--porcelain-4); border-bottom: 1px solid var(--porcelain-4); margin-bottom: 24px; }
.blog-share-links { display: flex; gap: 10px; }
.blog-share-btn { width: 32px; height: 32px; border-radius: 50%; border: 1px solid var(--porcelain-4); background: var(--porcelain-2); color: var(--ink-2); display: flex; align-items: center; justify-content: center; cursor: pointer; transition: border-color .15s, color .15s; }
.blog-share-btn:hover { border-color: var(--azure); color: var(--azure); }
.blog-share-btn svg { width: 16px; height: 16px; }
.blog-share-copy.is-copied { border-color: var(--azure); color: var(--azure); }
.blog-article { font-size: clamp(15px, 1.05vw, 17px); line-height: 1.7; color: var(--ink); max-width: 72ch; }
.blog-article h2 { font-size: clamp(19px, 1.4vw, 23px); margin: 1.8em 0 .6em; }
.blog-article h3 { font-size: clamp(16px, 1.15vw, 19px); margin: 1.5em 0 .5em; }
.blog-article p { margin: 0 0 1em; }
.blog-article ul, .blog-article ol { margin: 0 0 1em; padding-left: 1.4em; }
.blog-article blockquote { border-left: 3px solid var(--azure); padding-left: 16px; color: var(--ink-2); margin: 1.4em 0; }
.blog-article a { color: var(--azure); }
.blog-sidebar { padding-top: 8px; }
.blog-sidebar-title { font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); margin: 0 0 12px; }
.blog-side-list { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: 12px; }
.blog-side-card { display: flex; flex-direction: column; gap: 3px; text-decoration: none; padding-bottom: 12px; border-bottom: 1px solid var(--porcelain-4); }
.blog-side-card-title { font-size: 13px; font-weight: 600; color: var(--ink); line-height: 1.3; }
.blog-side-card:hover .blog-side-card-title { color: var(--azure); }
.blog-side-card-date { font-family: var(--mono); font-size: 10.5px; color: var(--ink-3); }
.blog-404-code { font-family: var(--display); font-size: 64px; font-weight: 600; color: var(--porcelain-4); margin: 0; line-height: 1; }

@media (max-width: 1100px) {
  .blog-post-layout { grid-template-columns: 1fr; }
  .blog-sidebar { border-top: 1px solid var(--porcelain-4); padding-top: 18px; }
}

/* comments */
.blog-comments { margin-top: 48px; border-top: 1px solid var(--porcelain-4); padding-top: 24px; }
.blog-comments-title { font-size: clamp(17px, 1.2vw, 20px); margin: 0 0 16px; }
.blog-comments-empty { color: var(--ink-3); font-size: 14px; }
.blog-comments-list { display: flex; flex-direction: column; gap: 16px; margin-bottom: 28px; }
.blog-comment { border: 1px solid var(--porcelain-4); border-radius: 10px; padding: 14px 16px; background: var(--porcelain-2); }
.blog-comment-meta { display: flex; justify-content: space-between; font-family: var(--mono); font-size: 11px; color: var(--ink-3); margin: 0 0 6px; }
.blog-comment-name { color: var(--ink); font-weight: 600; }
.blog-comment-text { font-size: 14px; line-height: 1.5; margin: 0; white-space: pre-wrap; }
.blog-comment-form { display: flex; flex-direction: column; gap: 12px; max-width: 480px; }
.blog-comment-field { display: flex; flex-direction: column; gap: 5px; }
.blog-comment-field label { font-family: var(--mono); font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--ink-3); }
.blog-comment-field input, .blog-comment-field textarea { background: var(--porcelain-2); border: 1px solid var(--porcelain-4); padding: 10px 12px; font-family: var(--body); font-size: 14px; color: var(--ink); border-radius: 8px; }
.blog-comment-field textarea { min-height: 90px; resize: vertical; }
.blog-comment-note { font-family: var(--mono); font-size: 11px; color: var(--ink-3); margin: 0; }
.blog-comment-status { font-family: var(--mono); font-size: 11.5px; color: var(--azure); min-height: 16px; margin: 0; }
.blog-comment-form button { align-self: flex-start; }

/* Header nav row used by _page-template.js's renderHeader() — a small
   inline row of links next to the existing .top header's brand, distinct
   from the deck's segmented progress strip (these pages don't have one). */
.blog-header-nav { display: flex; gap: 18px; align-items: center; }
.blog-header-nav a { font-family: var(--mono); font-size: 12px; letter-spacing: .04em; color: var(--ink-2); text-decoration: none; }
.blog-header-nav a:hover, .blog-header-nav a[aria-current="page"] { color: var(--azure); }
@media (max-width: 720px) { .blog-header-nav { display: none; } }

/* admin-* : the /admin, /admin-intake, /admin-comments CMS pages. Plain
   utilitarian table/form chrome — internal tooling, not brand-critical,
   but themed with the same tokens as everything else so dark mode works. */
.admin-shell { max-width: 1100px; }
.admin-login { max-width: 360px; margin: 60px auto; display: flex; flex-direction: column; gap: 14px; }
.admin-toolbar { display: flex; align-items: center; justify-content: space-between; gap: 16px; flex-wrap: wrap; margin-bottom: 18px; }
.admin-tabs { display: flex; gap: 16px; border-bottom: 1px solid var(--porcelain-4); margin-bottom: 20px; }
.admin-tab { background: none; border: 0; border-bottom: 2px solid transparent; padding: 8px 2px 10px; font-family: var(--mono); font-size: 12.5px; letter-spacing: .04em; text-transform: uppercase; color: var(--ink-3); cursor: pointer; }
.admin-tab.is-active { color: var(--azure); border-bottom-color: var(--azure); }
.admin-table-wrap { overflow-x: auto; border: 1px solid var(--porcelain-4); border-radius: 10px; }
.admin-table { width: 100%; border-collapse: collapse; font-size: 13.5px; }
.admin-table th { text-align: left; font-family: var(--mono); font-size: 10.5px; letter-spacing: .06em; text-transform: uppercase; color: var(--ink-3); padding: 10px 12px; border-bottom: 1px solid var(--porcelain-4); background: var(--porcelain-3); }
.admin-table td { padding: 10px 12px; border-bottom: 1px solid var(--porcelain-4); vertical-align: top; }
.admin-table tr:last-child td { border-bottom: 0; }
.admin-table tr:hover { background: var(--row-hover); }
.admin-badge { font-family: var(--mono); font-size: 10px; text-transform: uppercase; letter-spacing: .04em; padding: 2px 8px; border-radius: 20px; background: var(--porcelain-3); color: var(--ink-2); display: inline-block; }
.admin-badge.is-published, .admin-badge.is-approved { color: var(--azure); background: var(--azure-tint); }
.admin-badge.is-pending, .admin-badge.is-new { color: var(--ink-2); }
.admin-badge.is-rejected, .admin-badge.is-closed { color: var(--red); }
.admin-actions { display: flex; gap: 8px; flex-wrap: wrap; }
.admin-btn { font-family: var(--mono); font-size: 11.5px; padding: 5px 10px; border-radius: 6px; border: 1px solid var(--porcelain-4); background: var(--porcelain-2); color: var(--ink); cursor: pointer; }
.admin-btn:hover { border-color: var(--azure); color: var(--azure); }
.admin-btn.is-danger:hover { border-color: var(--red); color: var(--red); }
.admin-modal-backdrop { position: fixed; inset: 0; background: rgba(10, 20, 40, .5); display: flex; align-items: center; justify-content: center; z-index: 200; }
.admin-modal { background: var(--porcelain-2); border-radius: 14px; padding: 24px; max-width: 640px; width: calc(100% - 40px); max-height: 80vh; overflow-y: auto; }
.admin-editor { display: flex; flex-direction: column; gap: 14px; }
.admin-empty { color: var(--ink-3); padding: 24px; text-align: center; }
