@import url('logo.css?v=20260608-lockup-align');

/* ── Design Tokens ───────────────────────────────────────────────────────────
   Single source of truth for brand colours. Every file should reference these
   variables instead of hardcoding hex values.
   ─────────────────────────────────────────────────────────────────────────── */
:root {
  /* Brand */
  --c-primary:         #00f0ff;
  --c-primary-rgb:     0, 240, 255;
  --c-secondary:       #39ff88;
  --c-secondary-rgb:   57, 255, 136;
  --c-purple:          #7000ff;
  --c-purple-rgb:      112, 0, 255;

  /* Surfaces */
  --c-bg:              #050505;
  --c-overlay:         rgba(8, 10, 14, 0.97);

  /* Text */
  --c-text:            #e3e2e2;
  --c-text-rgb:        227, 226, 226;
  --c-text-2:          #c4c7c7;
  --c-text-3:          #8e9192;
  --c-text-inv:        #050505;

  /* Borders */
  --c-border:          rgba(255, 255, 255, 0.1);
  --c-border-2:        rgba(255, 255, 255, 0.18);

  /* Gradients */
  --g-primary:         linear-gradient(90deg, #00f0ff, #7000ff);
  --g-secondary:       linear-gradient(90deg, #39ff88, #00f0ff);
}

/* ── Button System ───────────────────────────────────────────────────────────
   Base class + variant modifiers. Replaces per-component button rules.
   Usage: class="btn btn--primary"  /  class="btn btn--ghost btn--sm"
   ─────────────────────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  border: 0;
  border-radius: 0.65rem;
  padding: 0.9rem 1.5rem;
  font: 800 0.8rem "JetBrains Mono", monospace;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  text-decoration: none;
  cursor: pointer;
  white-space: nowrap;
  transition: transform 0.22s ease, box-shadow 0.22s ease, opacity 0.22s ease;
}
.btn:hover  { transform: translateY(-2px); }
.btn:active { transform: translateY(0) scale(0.97); }
.btn:disabled,
.btn[aria-disabled="true"] { opacity: 0.45; pointer-events: none; cursor: not-allowed; }
.btn:focus-visible { outline: 2px solid var(--c-primary); outline-offset: 3px; }

.btn--primary {
  background: var(--g-primary);
  color: var(--c-text-inv);
  box-shadow: 0 0 20px rgba(var(--c-primary-rgb), 0.18);
}
.btn--primary:hover {
  box-shadow: 0 4px 32px rgba(var(--c-primary-rgb), 0.3);
}
.btn--secondary {
  background: var(--g-secondary);
  color: var(--c-text-inv);
}
.btn--ghost {
  background: rgba(255, 255, 255, 0.07);
  color: var(--c-text);
  border: 1px solid var(--c-border-2);
}
.btn--ghost:hover { background: rgba(255, 255, 255, 0.11); transform: translateY(-1px); }
.btn--outline {
  background: transparent;
  color: var(--c-primary);
  border: 1px solid rgba(var(--c-primary-rgb), 0.45);
}
.btn--outline:hover { background: rgba(var(--c-primary-rgb), 0.08); transform: translateY(-1px); }

/* Size modifiers */
.btn--sm   { padding: 0.5rem 1rem; font-size: 0.72rem; }
.btn--lg   { padding: 1rem 2rem; font-size: 0.9rem; }
.btn--pill { border-radius: 999px; }
.btn--full { width: 100%; }

@media (prefers-reduced-motion: reduce) {
  .btn, .btn:hover, .btn:active { transform: none !important; transition: opacity 0.2s ease; }
}

/* Shared nav Contact Us CTA glow. Previously duplicated inline on a handful of
   pages instead of centralized, so pages without their own copy rendered the
   button without the glow. nav.css loads sitewide, so this is now the single
   source of truth. */
.glow-cyan { box-shadow: 0 0 20px rgba(0, 240, 255, 0.3); }

/* ── Page reveal ─────────────────────────────────────────────────────────────
   nav.css is the first render-blocking stylesheet on every page, guaranteeing
   html { opacity: 0 } and the black root background are applied before the
   browser's first paint.

   Pages that load surette-logo.js: that script adds class="sds-logo-loaded"
   to <html> synchronously at parse time, suppressing the CSS animation below.
   surette-logo.js then reveals the page via JS (document.documentElement
   .style.opacity = '1') after DOMContentLoaded + 2 rAFs — at which point both
   logo canvases are mounted and the nav is at its full painted size.

   Portal/utility pages that do NOT load surette-logo.js (account-settings,
   account-created, app-checkout):
   they never get .sds-logo-loaded, so the CSS animation below fires at 200ms.
   Those pages have no SDS logo canvas, so there's nothing to hide.
   ─────────────────────────────────────────────────────────────────────────── */
@keyframes dm-page-show { to { opacity: 1; } }

html {
  opacity: 0;
  transition: opacity 0.38s ease;
  /* Matches the site's void-black body background. Without this, the browser
     shows white <html> between page navigations. */
  background-color: #050505;
  color: #ffffff;
  min-height: 100%;
  color-scheme: dark;
}

body {
  background-color: #050505;
  color: var(--c-text);
  min-height: 100vh;
}

body > main {
  min-height: 100vh;
}

/* Fixed headers must anchor to the viewport, even when they appear inside a
   padded wrapper such as the hand-authored mobile service-page shell. */
nav.fixed.top-0.w-full,
header.fixed.top-0.w-full {
  left: 0;
  right: 0;
  box-sizing: border-box;
}

/* Compact footer used by hand-authored mobile service-page markup. */
.service-mobile-footer {
  margin-top: 3.5rem;
  padding: 2rem 0 2.5rem;
  border-top: 1px solid rgba(255, 255, 255, 0.1);
}

.service-mobile-footer__brand {
  margin: 0 0 0.9rem;
  color: #ffffff;
  font: 800 1.05rem "Space Grotesk", sans-serif;
  letter-spacing: 0.1em;
  text-transform: uppercase;
}

.service-mobile-footer__meta {
  margin: 0;
  color: var(--c-text-3);
  font: 700 0.68rem/1.8 "JetBrains Mono", monospace;
  letter-spacing: 0.08em;
  text-transform: uppercase;
}

.service-mobile-footer__meta a,
.service-mobile-footer__links a {
  color: var(--c-primary);
  text-decoration: none;
}

.service-mobile-footer__links {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 1.4rem;
  margin-top: 1.6rem;
}

.service-mobile-footer__links span {
  display: block;
  margin-bottom: 0.8rem;
  color: #ffffff;
  font: 800 0.68rem "JetBrains Mono", monospace;
  letter-spacing: 0.16em;
  text-transform: uppercase;
}

.service-mobile-footer__links a {
  display: block;
  width: max-content;
  max-width: 100%;
  margin-top: 0.65rem;
  font: 700 0.75rem "JetBrains Mono", monospace;
  letter-spacing: 0.06em;
  overflow-wrap: anywhere;
}

#__next,
#root,
.app {
  background-color: #050505;
  min-height: 100vh;
}

#page-loader {
  position: fixed;
  inset: 0;
  z-index: 9999;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 16px;
  background: #050505;
  opacity: 1;
  pointer-events: none;
  transition: opacity 0.5s ease;
}

#page-loader.fade-out {
  opacity: 0;
}

#page-loader svg {
  animation: sds-loader-spin 0.9s linear infinite;
}

#page-loader span {
  color: rgba(0, 240, 255, 0.42);
  font: 700 10px Consolas, "Courier New", monospace;
  letter-spacing: 0.22em;
  text-transform: uppercase;
}

@keyframes sds-loader-spin {
  to { transform: rotate(360deg); }
}

/* CSS-only fallback for pages that do not load surette-logo.js. */
html:not(.sds-logo-loaded) {
  animation: dm-page-show 0s 0.2s forwards;
}

/* Safety fallback for logo pages if JS reveal is interrupted. */
html.sds-logo-loaded {
  animation: dm-page-show 0s 0.9s forwards;
}

/* Object reveal
   Keep repeated cards/panels hidden as whole objects until the page-ready
   marker is applied. This prevents child text from appearing before borders,
   backgrounds, blur, images, and other object styling have settled. */
html.sds-logo-loaded:not(.page-content-ready) :where(
  .glass,
  .glass-card,
  .panel,
  .card,
  .portfolio-tile,
  .portfolio-card,
  .pricing-banner,
  .pricing-tier-link,
  .feature-card,
  .service-card,
  .case-card,
  .checkout-panel,
  .account-card,
  .portal-card,
  .dashboard-card,
  .message-card,
  .stat-card,
  .sds-card,
  .app-card
) {
  opacity: 0;
  transform: translateY(10px);
}

html.page-content-ready :where(
  .glass,
  .glass-card,
  .panel,
  .card,
  .portfolio-tile,
  .portfolio-card,
  .pricing-banner,
  .pricing-tier-link,
  .feature-card,
  .service-card,
  .case-card,
  .checkout-panel,
  .account-card,
  .portal-card,
  .dashboard-card,
  .message-card,
  .stat-card,
  .sds-card,
  .app-card
) {
  opacity: 1;
  transform: none;
  transition: opacity 0.32s ease, transform 0.32s ease;
}

@media (prefers-reduced-motion: reduce) {
  html { transition: none; }
  html:not(.sds-logo-loaded) { animation: dm-page-show 0s 0s forwards; }
  html.sds-logo-loaded { animation: dm-page-show 0s 0s forwards; }
  html.sds-logo-loaded :where(
    .glass,
    .glass-card,
    .panel,
    .card,
    .portfolio-tile,
    .portfolio-card,
    .pricing-banner,
    .pricing-tier-link,
    .feature-card,
    .service-card,
    .case-card,
    .checkout-panel,
    .account-card,
    .portal-card,
    .dashboard-card,
    .message-card,
    .stat-card,
    .sds-card,
    .app-card
  ) {
    opacity: 1;
    transform: none;
    transition: none;
  }
}

/* Logo lockup — row layout so icon sits left of wordmark (not stacked) */
.site-brand {
  display: inline-flex;
  flex-direction: row;
  align-items: center;
  flex-wrap: nowrap;
  gap: 0.625rem;
  text-decoration: none;
  color: inherit;
  max-width: 100%;
}

.site-brand .brand-mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  flex: 0 0 2rem;
  width: 2rem;
  height: 2rem;
  font-size: 2rem;
  line-height: 1;
}

.site-brand > .flex.flex-col {
  flex: 0 1 auto;
  min-width: 0;
}

/* Explicit fallback for the label-mono / label-caps utility classes used
   throughout the nav (Home/Services/Portfolio/Client Login/Contact Us).
   These are custom Tailwind theme keys, not built-in utilities, so they only
   render correctly if a page's own tailwind.config defines them AND the
   Tailwind CDN's runtime JIT scanner catches the class on the element. Nav
   markup injected after page load by assets/standard-site-nav.js is not
   reliably caught by that scanner, and at least one page (account.html) was
   missing these theme keys from its config entirely, so nav text silently
   fell back to the page's default sans-serif font. Defining the concrete
   CSS here removes both failure modes for every page, regardless of its own
   Tailwind config.
   ─────────────────────────────────────────────────────────────────────────── */
.font-label-mono { font-family: 'JetBrains Mono', monospace; }
.text-label-mono { font-size: 14px; line-height: 1.4; letter-spacing: 0.05em; font-weight: 500; }
.font-label-caps { font-family: 'JetBrains Mono', monospace; }
.text-label-caps { font-size: 12px; line-height: 1.2; letter-spacing: 0.1em; font-weight: 700; }

/* Same failure mode, same fix, for the custom-theme color utilities used in
   the nav (Home/Services/Portfolio/Client Login/Contact Us and the mobile
   dropdown). Without these, an element can keep the default text color
   instead of showing as highlighted/cyan even when its class list is
   correct — e.g. the Account link reading as "unhighlighted" on pages where
   the JIT scanner missed it. */
.text-electric-cyan { color: #00F0FF; }
.text-nebula-purple { color: #7000FF; }
.text-on-surface-variant { color: #c4c7c7; }
.text-starlight-white { color: #FFFFFF; }
.text-outline { color: #8e9192; }
.text-void-black { color: #050505; }
.border-electric-cyan { border-color: #00F0FF; }
.bg-electric-cyan\/5 { background-color: rgba(0, 240, 255, 0.05); }

/* Same failure mode again, this time for the gradient-stop utilities on the
   Contact Us CTA. bg-gradient-to-r sets background-image from the
   --tw-gradient-from/--tw-gradient-to custom properties that from-* / to-*
   are supposed to set — when the JIT scanner misses from-electric-cyan and
   to-nebula-purple, background-image silently resolves to "none" and the
   button renders with no visible background at all. */
.bg-gradient-to-r.from-electric-cyan.to-nebula-purple {
  background-image: linear-gradient(90deg, #00F0FF, #7000FF);
}

.material-symbols-outlined {
  font-family: 'Material Symbols Outlined';
  font-weight: normal;
  font-style: normal;
  font-size: 24px;
  line-height: 1;
  letter-spacing: normal;
  text-transform: none;
  display: inline-block;
  white-space: nowrap;
  word-wrap: normal;
  direction: ltr;
  font-variation-settings: 'FILL' 0, 'wght' 400, 'GRAD' 0, 'opsz' 24;
}

/* ── Skeleton shimmer ────────────────────────────────────────────────────── */
@keyframes skel-shimmer {
  0%   { background-position: -800px 0; }
  100% { background-position:  800px 0; }
}
.skel-bar {
  display: block;
  border-radius: 6px;
  background: linear-gradient(
    90deg,
    rgba(255,255,255,.06) 0%,
    rgba(255,255,255,.14) 50%,
    rgba(255,255,255,.06) 100%
  );
  background-size: 1600px 100%;
  animation: skel-shimmer 1.8s ease-in-out infinite;
}
.skel-hero { pointer-events: none; user-select: none; }
html.page-content-ready .skel-hero { display: none !important; }

/* ── Hero content stagger ────────────────────────────────────────────────── */
.hero-enter {
  opacity: 0;
  transform: translateY(14px);
  transition: opacity .55s ease, transform .55s ease;
}
html.page-content-ready .hero-enter        { opacity: 1; transform: none; }
html.page-content-ready .hero-enter-d0     { transition-delay: .04s; }
html.page-content-ready .hero-enter-d1     { transition-delay: .16s; }
html.page-content-ready .hero-enter-d2     { transition-delay: .28s; }
html.page-content-ready .hero-enter-d3     { transition-delay: .42s; }

/* ── Scroll reveal ───────────────────────────────────────────────────────── */
.reveal-up {
  opacity: 0;
  transform: translateY(22px);
  transition: opacity .6s ease, transform .6s ease;
}
.reveal-up.in-view { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .skel-bar                                { animation: none; }
  .hero-enter                              { opacity: 1 !important; transform: none !important; transition: none; }
  .reveal-up                               { opacity: 1; transform: none; transition: none; }
}

/* Services dropdown */
.services-nav-group {
  position: relative;
  display: inline-flex;
  align-items: center;
  align-self: center;
}

/* Main menu links — same height for Services, Portfolio, and siblings */
nav .hidden.md\:flex {
  align-items: center;
}

nav .hidden.md\:flex > a.font-label-mono:not(.bg-gradient-to-r),
.services-nav-group > a.font-label-mono:first-child {
  display: inline-flex;
  align-items: center;
  align-self: center;
  padding-bottom: 0.25rem;
  border-bottom: 2px solid transparent;
  box-sizing: border-box;
  line-height: 1.4;
}

/* The Services trigger has no href (it opens a hover dropdown, not a page),
   so browsers show the default text-select cursor instead of a pointer. */
.services-nav-group > a:first-child {
  cursor: pointer;
}

nav .hidden.md\:flex > a.font-label-mono.border-b-2,
.services-nav-group > a.font-label-mono.border-b-2:first-child {
  border-bottom-color: var(--c-primary);
}

nav,
nav .hidden.md\:flex,
.services-nav-group {
  overflow: visible;
}

/* ───────────────────────────────────────────────────────────────
   Fluid desktop top-nav (≥768px), shared sitewide.

   Previously the brand and Contact CTA were absolutely positioned at a
   fixed side offset while the link cluster was centred across the full
   width, so on narrow desktop/tablet widths (≈768–1150px) the centred
   links slid under the brand and CTA and the text overlapped.

   Now the brand sits in normal flex flow (it can never overlap the
   links) and the link cluster is centred in the space that remains,
   clear of a right gutter reserved for the Contact CTA. The side inset,
   gutter, inter-link gap and link font-size all scale continuously via
   clamp(). The Contact CTA stays pinned to the right edge so the same
   rules work whether the markup places it as a direct nav child or
   nested at the end of the link cluster (both variants exist sitewide).
   The apps page overrides the gutter in surette-data-systems-apps.css
   (its CTA is a separate fixed overlay rather than a nav child).
   ─────────────────────────────────────────────────────────────── */
@media (min-width: 880px) {
  nav.fixed.top-0.w-full {
    /* Edge inset; tightens as the viewport narrows, reaches the
       original 4rem by standard desktop widths (~1280px). */
    --dm-nav-side-offset: clamp(1.25rem, 5vw, 4rem);
    /* Right gutter held open for the pinned Contact CTA so the centred
       link cluster can never slide underneath it. */
    --dm-nav-contact-reserve: 10.5rem;
    justify-content: flex-start !important;
    padding-left: var(--dm-nav-side-offset) !important;
    padding-right: calc(var(--dm-nav-side-offset) + var(--dm-nav-contact-reserve)) !important;
    column-gap: clamp(0.6rem, 1.6vw, 1.5rem);
  }

  /* Brand back into the flex flow (was position:absolute). */
  nav.fixed.top-0.w-full > .site-brand,
  nav.fixed.top-0.w-full > #sds-logo {
    position: static;
    left: auto;
    top: auto;
    transform: none;
    flex: 0 0 auto;
  }

  /* Link cluster centres between the brand and the reserved gutter; its
     internal spacing tightens fluidly. */
  nav.fixed.top-0.w-full > .hidden.md\:flex {
    margin-inline: auto;
    gap: clamp(0.55rem, 1.5vw, 2.5rem) !important;
    min-width: 0;
  }

  /* Link text eases down a little on narrow widths to buy room. */
  nav.fixed.top-0.w-full > .hidden.md\:flex > a.font-label-mono,
  nav.fixed.top-0.w-full > .hidden.md\:flex .services-nav-group > a,
  nav.fixed.top-0.w-full > .hidden.md\:flex .lang-switch {
    font-size: clamp(0.78rem, 0.66rem + 0.42vw, 0.875rem);
  }

  /* Contact CTA pinned to the right edge. Two selectors cover both DOM
     variants: a direct nav child, and a child nested at the end of the
     link cluster. Visual styling (size, colour, font) is preserved. */
  nav.fixed.top-0.w-full > a.bg-gradient-to-r,
  nav.fixed.top-0.w-full > .hidden.md\:flex > a.bg-gradient-to-r {
    position: absolute;
    right: var(--dm-nav-side-offset);
    top: 50%;
    transform: translateY(-50%);
    display: inline-flex !important;
    align-items: center;
    justify-content: center;
    min-height: 2.225rem;
    padding: 0.5rem 1.5rem !important;
    border-radius: 0.5rem !important;
    color: #fff !important;
    font-family: 'JetBrains Mono', monospace !important;
    font-size: 0.875rem !important;
    font-weight: 500 !important;
    line-height: 1.4 !important;
    letter-spacing: 0.05em !important;
    text-decoration: none !important;
    white-space: nowrap !important;
    margin-left: 0 !important;
    box-shadow: 0 0 24px rgba(0, 240, 255, 0.18);
  }

  nav.fixed.top-0.w-full > a.bg-gradient-to-r:hover,
  nav.fixed.top-0.w-full > .hidden.md\:flex > a.bg-gradient-to-r:hover {
    transform: translateY(-50%) scale(1.05);
  }

  nav.fixed.top-0.w-full > a.bg-gradient-to-r:active,
  nav.fixed.top-0.w-full > .hidden.md\:flex > a.bg-gradient-to-r:active {
    transform: translateY(-50%) scale(0.95);
  }
}

/* Where the header switches to the hamburger.
   The wide header only fits its full link row plus the Contact CTA down to
   ~880px. It used to keep going to 768px by dropping the CTA, which left a
   half-collapsed header: links on the left, a dead gutter on the right where
   the button had been, and no way to reach Contact from the header at all.
   Switch to the hamburger at that point instead, so the header is either whole
   or a menu and never partway.

   The injected nav's own classes are `hidden md:flex` / `md:hidden`, i.e. baked
   to Tailwind's 768px. These two rules move that boundary for the nav only —
   `.dm-standard-mobile-shell` is on the injected shell specifically, so page
   layouts that use the same `md:hidden` class for their phone wrapper are not
   dragged along and keep switching at 768px. */
@media (max-width: 879.98px) {
  .dm-standard-desktop-nav {
    display: none !important;
  }

  .dm-standard-mobile-shell {
    display: block !important;
  }
}

/* Tint for the three fixed injected bars (desktop header, phone header, phone
   bottom tab bar).

   These float over the page, so whatever scrolls behind them decides whether
   their labels survive — and both galleries now pass bright pastel cards and
   near-white screenshots underneath. At the old `bg-surface/10` the bar labels
   measured **1.17:1** against a light card and the active cyan item 1.09:1:
   not dim, invisible. Over the site's dark background the same labels measure
   4.68:1, which is the intended look.

   0.9 restores almost all of that (label ~4.25:1 over a near-white backdrop,
   active ~11:1) while keeping the blur and a little colour bleed. It is cheap
   to be this dark: the tint is within a few points of the page background, so
   over normal dark pages every alpha from 0.1 to 1.0 measures the same 4.68 —
   this only bites where light content is behind, which is the whole point.

   Set here in plain CSS rather than as a Tailwind utility on injected markup,
   per DECISIONS: the CDN only builds classes it finds in a page's static HTML,
   and `bg-surface/10` appeared in none of them — it was surviving on the CDN's
   DOM observer, the same bet that already rendered a footer line white and a
   hamburger invisible. `!important` matches this file's established pattern for
   injected nav markup. */
.dm-standard-desktop-nav,
.dm-unified-mobile-header,
.dm-standard-bottom-nav {
  background: rgba(8, 10, 14, 0.9) !important;
}

/* Hairline borders for the same three bars, pinned here for the same reason as
   the tint above: the injected markup's `border-white/10` /
   `border-starlight-white/10` utilities exist in no page's static HTML, so
   they ride on the CDN's DOM observer — which missed on portfolio.html and
   left both bars with Tailwind's preflight fallback border (#e5e7eb), a solid
   light-gray 1px line across the header bottom and above the footer. */
.dm-standard-desktop-nav,
.dm-unified-mobile-header {
  border-bottom: 1px solid rgba(255, 255, 255, 0.1) !important;
}

.dm-standard-bottom-nav {
  border-top: 1px solid rgba(255, 255, 255, 0.1) !important;
}

.services-nav-menu {
  position: absolute;
  top: 100%;
  left: 0;
  padding-top: 8px;
  opacity: 0;
  visibility: hidden;
  transform: translateY(-0.65rem) scale(0.985);
  transform-origin: top left;
  transition:
    opacity 0.36s ease,
    transform 0.42s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s linear 0.36s;
  pointer-events: none;
  z-index: 80;
}

/* Invisible bridge so hover is not lost between trigger and menu */
.services-nav-menu::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 10px;
}

.services-nav-group:hover .services-nav-menu,
.services-nav-group:focus-within .services-nav-menu {
  opacity: 1;
  visibility: visible;
  transform: translateY(0) scale(1);
  transition:
    opacity 0.3s ease,
    transform 0.42s cubic-bezier(0.22, 1, 0.36, 1),
    visibility 0s;
  pointer-events: auto;
}

.services-nav-panel {
  background: rgba(8, 10, 14, 0.97);
  backdrop-filter: blur(20px) saturate(1.2);
  -webkit-backdrop-filter: blur(20px) saturate(1.2);
  border: 1px solid rgba(255, 255, 255, 0.12);
  box-shadow: 0 18px 48px rgba(0, 0, 0, 0.65);
  border-radius: 0.75rem;
  padding: 0.65rem 0;
  min-width: 18rem;
  overflow: hidden;
}

.services-nav-heading {
  display: flex;
  align-items: center;
  gap: 0.45rem;
  padding: 0.55rem 1rem 0.35rem;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.625rem;
  line-height: 1.2;
  letter-spacing: 0.16em;
  text-transform: uppercase;
  color: var(--c-text-3);
}

.services-nav-heading:not(:first-child) {
  margin-top: 0.4rem;
  padding-top: 0.75rem;
  border-top: 1px solid rgba(255, 255, 255, 0.08);
}

.services-nav-heading .material-symbols-outlined,
.services-nav-heading-icon {
  display: inline-flex !important;
  align-items: center !important;
  justify-content: center !important;
  flex: 0 0 1rem !important;
  width: 1rem !important;
  height: 1rem !important;
  font-size: 1rem !important;
  line-height: 1 !important;
  color: var(--c-primary) !important;
  opacity: 0.78 !important;
  vertical-align: middle !important;
  transform: translateY(0) !important;
  font-variation-settings: 'FILL' 0, 'wght' 350, 'GRAD' 0, 'opsz' 20 !important;
}

.services-nav-panel a {
  display: block;
  padding: 0.625rem 1rem;
  font-family: 'JetBrains Mono', monospace;
  font-size: 0.875rem;
  line-height: 1.4;
  color: var(--c-text-2);
  transition: color 0.24s ease, background-color 0.24s ease;
}

.services-nav-panel a:hover {
  color: var(--c-primary);
  background: rgba(255, 255, 255, 0.06);
}

.services-nav-panel a.text-electric-cyan {
  color: var(--c-primary);
  background: rgba(var(--c-primary-rgb), 0.06);
}

/* Mobile nav chrome. Runs to 880px, not 768px, because that is where the
   header switches to the hamburger (see the switch rules above) — the dropdown,
   popout and bottom bar have to be styled wherever they are actually shown.
   Page-layout rules do NOT belong in here; they keep the 767px phone
   breakpoint and live in their own block further down. */
@media (max-width: 879.98px) {
  html,
  body {
    overflow-x: hidden;
  }

  .md\:hidden header {
    height: 4rem;
  }

  .md\:hidden header .lang-switch {
    margin-left: auto;
    padding-right: 0.35rem;
  }

  .md\:hidden main {
    padding-top: 5.25rem !important;
  }

  .md\:hidden main .hero-headline-band {
    padding-top: clamp(3.2rem, 8vh, 4.4rem) !important;
  }

  .mobile-services-popout {
    position: fixed;
    left: 0.65rem;
    right: 0.65rem;
    bottom: 5.75rem;
    z-index: 70;
    max-height: min(72vh, 34rem);
    overflow: auto;
    padding: 0.8rem;
    border: 1px solid rgba(255, 255, 255, 0.12);
    border-radius: 0.85rem;
    background: rgba(8, 10, 14, 0.94);
    backdrop-filter: blur(24px) saturate(1.25);
    -webkit-backdrop-filter: blur(24px) saturate(1.25);
    box-shadow: 0 24px 70px rgba(0, 0, 0, 0.72), 0 0 26px rgba(0, 240, 255, 0.12);
    opacity: 0;
    visibility: hidden;
    transform: translateY(-0.7rem) scale(0.985);
    transform-origin: top center;
    pointer-events: none;
    transition:
      opacity 0.3s ease,
      transform 0.38s cubic-bezier(0.22, 1, 0.36, 1),
      visibility 0s linear 0.3s;
  }

  .mobile-nav-dropdown {
    opacity: 0;
    visibility: hidden;
    transform: translateY(-8px) scale(0.96);
    pointer-events: none;
    transition:
      opacity 0.28s cubic-bezier(0.22, 1, 0.36, 1),
      transform 0.32s cubic-bezier(0.22, 1, 0.36, 1),
      visibility 0.32s;
  }

  .mobile-nav-dropdown.is-open {
    opacity: 1;
    visibility: visible;
    transform: translateY(0) scale(1);
    pointer-events: auto;
  }

  .mobile-nav-dropdown-panel {
    background: rgba(8, 10, 14, 0.96);
    backdrop-filter: blur(20px) saturate(1.25);
    -webkit-backdrop-filter: blur(20px) saturate(1.25);
    border: 1px solid rgba(255, 255, 255, 0.12);
    box-shadow: 0 18px 48px rgba(0, 0, 0, 0.65);
  }

  .dm-unified-mobile-header {
    left: 0;
    right: 0;
    width: auto;
  }

  .dm-unified-mobile-header-content {
    padding-inline: clamp(1rem, 5.333vw, 2rem) !important;
  }

  .dm-mobile-menu-controls {
    margin-left: auto;
    min-width: 0;
  }

  /* The three hamburger lines take their colour from Tailwind utilities
     (bg-electric-cyan, bg-starlight-white/55, bg-starlight-white/35) sitting on
     injected nav markup, and the CDN only builds a utility it can find in the
     page's own static HTML. portfolio.html never used them anywhere else, so
     the lines resolved to transparent: the menu button was present and
     clickable but completely invisible. Pin the colours here, where the rest of
     the injected nav's fallbacks already live. Same values the utilities
     produce, so pages where the CDN does generate them are unaffected. */
  .mobile-menu-line-top { background-color: #00f0ff; }
  .mobile-menu-line-mid { background-color: rgba(255, 255, 255, 0.55); }
  .mobile-menu-line-bot { background-color: rgba(255, 255, 255, 0.35); }

  #mobile-menu-btn[aria-expanded="true"] .mobile-menu-line-top { width: 1.25rem; }
  #mobile-menu-btn[aria-expanded="true"] .mobile-menu-line-mid { width: 1rem; opacity: 0.9; }
  #mobile-menu-btn[aria-expanded="true"] .mobile-menu-line-bot { width: 0.75rem; opacity: 0.75; }

  .mobile-acc-panel {
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.34s cubic-bezier(0.22, 1, 0.36, 1);
  }

  .mobile-acc-panel.is-open { max-height: 30rem; }
  .mobile-acc-chevron { transition: transform 0.3s ease; }
  .mobile-acc-toggle[aria-expanded="true"] .mobile-acc-chevron { transform: rotate(180deg); }
  .mobile-acc-toggle[aria-expanded="true"] { color: #fff; }

  /* Small non-interactive category tag inside the "All Services" accordion
     panel — same visual language as .mobile-services-popout__heading (the
     bottom-nav Services popout, which already got this right), just indented
     to nest under the All Services toggle instead of being its own button. */
  .mobile-menu-group-label {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.65rem 1rem 0.35rem 1.75rem;
    color: var(--c-primary);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.62rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
  }
  .mobile-menu-group-label:not(:first-child) {
    margin-top: 0.35rem;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
  }

  .mobile-services-popout.is-open {
    opacity: 1;
    visibility: visible;
    transform: translateY(0) scale(1);
    transition:
      opacity 0.28s ease,
      transform 0.38s cubic-bezier(0.22, 1, 0.36, 1),
      visibility 0s;
    pointer-events: auto;
  }

  .mobile-services-popout__grid {
    display: grid;
    grid-template-columns: 1fr;
    gap: 0.65rem;
  }

  .mobile-services-popout__group {
    border: 1px solid rgba(255, 255, 255, 0.08);
    border-radius: 0.65rem;
    overflow: hidden;
    background: rgba(255, 255, 255, 0.035);
  }

  .mobile-services-popout__heading {
    display: flex;
    align-items: center;
    gap: 0.45rem;
    padding: 0.65rem 0.8rem 0.45rem;
    color: var(--c-primary);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.62rem;
    letter-spacing: 0.14em;
    text-transform: uppercase;
  }

  .mobile-services-popout a {
    display: block;
    padding: 0.55rem 0.8rem;
    color: var(--c-text-2);
    font-family: 'JetBrains Mono', monospace;
    font-size: 0.72rem;
    line-height: 1.25;
    border-top: 1px solid rgba(255, 255, 255, 0.06);
  }

  .mobile-services-popout a:active,
  .mobile-services-popout a:hover {
    color: #fff;
    background: rgba(var(--c-primary-rgb), 0.08);
  }

  /* The bottom tab bar is fixed, so it reserves no space of its own — it simply
     covers the last 5rem of the page, and footers were ending underneath it
     (Privacy/Terms/Accessibility were unreachable on the portfolio detail pages,
     Accessibility on the pricing page). The homepage already solved this with a
     `mb-20` on its footer — 5rem, exactly the bar's height — so this applies
     that same idiom everywhere instead of asking each page to remember it.
     Deliberately a margin on the footer rather than padding on the body: the
     footer then still ends flush against the bar, where padding would push it
     up and expose a strip of the fixed starfield underneath it. Pages already
     using mb-20 resolve to the identical 5rem and so are unchanged. */
  body.has-dm-standard-nav footer {
    margin-bottom: calc(5rem + env(safe-area-inset-bottom, 0px));
  }

}

/* These two are page layout, not nav chrome, so they stay on the real phone
   breakpoint: between 768px and 880px the page still renders its wide layout
   (only the header switches to the hamburger), and forcing 20px gutters there
   would squeeze the app pages for no reason. */
@media (max-width: 879.98px) {
  body.has-dm-standard-nav .page {
    padding: 7.75rem 20px 5.5rem !important;
  }

  body.has-dm-standard-nav .app-footer {
    padding-bottom: 2rem !important;
  }
}

@media (min-width: 880px) {
  .dm-standard-desktop-nav {
    left: 0;
    right: 0;
    width: auto;
  }
}

/* ── Shared site footer (injected by assets/standard-site-footer.js) ─────────
   Plain CSS rather than the Tailwind utilities the homepage footer was written
   with, because the CDN only builds utilities it can see in a page's static
   HTML: once the footer is injected these classes exist nowhere else, and
   `text-on-primary-container/60` silently rendered white when tested that way.
   Every value here is what the homepage footer already resolved to.

   Two variants, matching the homepage: .dm-footer from 768px up (so tablet
   gets the wide one) and .dm-footer-mobile below it. Clearance above the fixed
   bottom tab bar is not set here — `body.has-dm-standard-nav footer` above
   already owns that for every footer on the site.
   ─────────────────────────────────────────────────────────────────────────── */
.dm-footer,
.dm-footer-mobile {
  width: 100%;
  position: relative;
  z-index: 10;
  background: #050505;
  border-top: 1px solid rgba(255, 255, 255, 0.05);
}

.dm-footer {
  display: none;
  margin-top: auto;
  padding: 48px 64px;
}

.dm-footer__inner {
  max-width: 1440px;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  justify-content: space-between;
  gap: 40px;
}

.dm-footer__brand {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.dm-footer__wordmark {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 24px;
  font-weight: 500;
  line-height: 1.3;
  color: #ffffff;
}

/* Phrase-atomic, same as the legal block below. This column is the one that
   gives up width once the legal block stops shrinking, so the tagline is the
   most likely thing in the footer to wrap — it should break between phrases,
   not strand "Florida" from "Southwest". */
.dm-footer__tagline {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 6px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  line-height: 1.5;
  letter-spacing: 0.2em;
  text-transform: uppercase;
  color: #8e9192;
}

.dm-footer__tagline > * {
  white-space: nowrap;
}

.dm-footer__links {
  display: flex;
  flex-wrap: wrap;
  gap: 16px 40px;
}

.dm-footer__links a {
  font-family: 'JetBrains Mono', monospace;
  font-size: 14px;
  font-weight: 500;
  line-height: 1.4;
  letter-spacing: 0.05em;
  color: #8e9192;
  text-decoration: none;
  transition: color 0.3s ease;
}

.dm-footer__links a:hover {
  color: #00f0ff;
}

/* Legal/meta block. Each line is a flex row of atomic phrases (see the
   `phrases()` helper in standard-site-footer.js): it wraps *between* phrases
   when the column is tight and never inside one. As one plain string it broke
   at any space, which is what split the phone across two lines as "404-" /
   "8505" — and at 1440px, not just on phones. */
.dm-footer__legal {
  display: flex;
  flex-direction: column;
  align-items: flex-start;
  gap: 4px;
}

.dm-footer__legal-line {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 0 6px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  line-height: 1.5;
  color: #8e9192;
}

.dm-footer__legal-line > * {
  white-space: nowrap;
}

.dm-legal-sep {
  opacity: 0.45;
}

.dm-footer__legal a {
  color: inherit;
  text-decoration: none;
  transition: color 0.3s ease;
}

.dm-footer__legal a:hover {
  color: #00f0ff;
}

@media (min-width: 880px) {
  .dm-footer {
    display: block;
  }
}

/* The three columns only sit side by side once there is room; 768–1023px keeps
   them stacked, which is what the homepage footer already did at tablet. */
@media (min-width: 1024px) {
  .dm-footer__inner {
    flex-direction: row;
  }

  /* Once the three columns are side by side, the link list is the elastic one —
     it already wraps. This column must not be: `space-between` was shrinking it
     to 159px even at 1440px, well under the 282px its two lines need, which is
     what forced the phone to break. Let it take its content and make the links
     wrap a row earlier instead. */
  .dm-footer__legal {
    align-items: flex-end;
    flex-shrink: 0;
  }

  .dm-footer__legal-line {
    justify-content: flex-end;
  }
}

.dm-footer-mobile {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 24px;
  padding: 48px 20px;
}

@media (min-width: 880px) {
  .dm-footer-mobile {
    display: none;
  }
}

.dm-footer-mobile__brand {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
  opacity: 0.8;
}

.dm-footer-mobile__brand .material-symbols-outlined {
  font-size: 20px;
  line-height: 28px;
  color: #00f0ff;
}

.dm-footer-mobile__wordmark {
  font-family: 'Space Grotesk', sans-serif;
  font-size: 24px;
  font-weight: 500;
  line-height: 1.3;
  letter-spacing: -0.05em;
  color: #ffffff;
}

.dm-footer-mobile__links {
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  gap: 8px 24px;
  text-align: center;
}

.dm-footer-mobile__links a {
  font-family: 'JetBrains Mono', monospace;
  font-size: 11px;
  line-height: 1.5;
  color: #8e9192;
  text-decoration: none;
  transition: color 0.3s ease;
}

.dm-footer-mobile__links a:hover {
  color: #00f0ff;
}

/* Same phrase-atomic treatment as the wide footer's legal block: centred text
   is the easiest place to notice a word breaking away from its phrase. */
.dm-footer-mobile__legal {
  margin: 0;
  max-width: 32rem;
  display: flex;
  flex-wrap: wrap;
  justify-content: center;
  align-items: baseline;
  gap: 0 6px;
  font-family: 'JetBrains Mono', monospace;
  font-size: 10px;
  line-height: 1.5;
  letter-spacing: 0.1em;
  text-align: center;
  color: rgba(121, 119, 119, 0.6);
}

.dm-footer-mobile__legal > * {
  white-space: nowrap;
}

/* ── Tablet band (768px–1279px): rail inset + stacked columns ── */
@media (min-width: 880px) and (max-width: 1279.98px) {
  /* Left rail padding is for wide desktop; at tablet it steals horizontal room. */
  .md\:pl-28 {
    padding-left: clamp(1.25rem, 4vw, 4rem) !important;
  }

  /* Contact and similar two-column layouts: stack until lg. */
  main .max-w-container-max.flex.flex-col.md\:flex-row {
    flex-direction: column !important;
  }

  main .max-w-container-max.flex.flex-col.md\:flex-row > * {
    min-width: 0;
    width: 100%;
  }

  /* Portfolio detail hero rows with live-site widget. */
  section .flex.flex-col.md\:flex-row.md\:items-end.justify-between {
    flex-direction: column !important;
    align-items: stretch !important;
  }
}

/* ── Footer copyright/contact line ──────────────────────────────────────────
   Wraps as whole phrases (flex-wrap items) instead of breaking mid-phrase at
   arbitrary word boundaries — the bug that let "Call or text" split away
   from the phone number on narrow screens. Each phrase's own text still
   wraps internally if it has to; the phone number and email stay nowrap
   since a broken phone number or email address reads as visibly wrong. */
.footer-meta { display:flex; flex-wrap:wrap; align-items:center; gap:5px 9px; }
.footer-meta-sep { opacity:.45; }
.footer-meta a[href^="tel:"], .footer-meta a[href^="mailto:"] { white-space:nowrap; }

/* ── "Client Login" nav link ─────────────────────────────────────────────────
   Signed-out is a normal (non-highlighted) nav link; account-nav.js adds
   .is-signed-in only once an actual session is detected. Previously this
   link was hardcoded cyan/highlighted unconditionally, which read as "the
   user is logged in" even when they weren't. Not scoped to a media query —
   the desktop/mobile variants are only ever visible in their own nav anyway.
   !important guards against Tailwind's CDN-JIT utility classes winning a
   cascade tie on dynamically injected markup (a recurring issue on this
   site — see CHANGELOG for the color-utility-fallback fixes). */
.dm-account-link-desktop.is-signed-in { color: #00F0FF !important; }
.dm-account-link-desktop.is-signed-in:hover { color: #fff !important; }
.dm-account-link-mobile.is-signed-in { color: #00F0FF !important; background-color: rgba(0, 240, 255, 0.05) !important; }
