/* ============================================================
   LaingLogic - site stylesheet
   ============================================================ */

/* ── Keyframes, @font-face, @property ── */

/* Silkscreen - the Dashboard's pixel font, self-hosted (copied from
   Dashboard/public/assets/fonts). 2026-07-31: body text is Google-Fonts
   Onest (was Inter until 2026-07-31; Onest measured closest to Inter's
   line metrics of the candidates - 8 single-line paragraph deltas
   site-wide, none gained, nothing overflows); Silkscreen is used ONLY on
   the "//" top-subtitle labels (see the pixel-accent rule at the end of
   this file). */
@font-face {
  font-family: 'Silkscreen';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('../assets/fonts/silkscreen400Latin.woff2') format('woff2');
}
@font-face {
  font-family: 'Silkscreen';
  font-style: normal;
  font-weight: 700;
  font-display: swap;
  src: url('../assets/fonts/silkscreen700Latin.woff2') format('woff2');
}

/* ── Shared keyframes (used across multiple pages) ── */
/* Skeleton shimmer: one left-to-right pass, normalised to the element.

   With background-size:200%, a position of X% resolves against
   (boxW - imgW) = -boxW, so the band's centre sits at boxW * (1 - X/100).
   It is therefore ON the element exactly while X runs 100% -> 0%, and the
   25%/75% gradient stops give the bright core a half-width of ~0.5 boxW.
   Hence 150% -> -50%: the band enters at the left edge, crosses, and exits
   at the right edge with no dead time. (A wider range like 200% -> -200%
   parks the band off-element for three quarters of every cycle, which reads
   as an occasional flash rather than a steady sweep - measured, not
   guessed.)

   Decreasing percentage moves the band RIGHTWARD, because (boxW - imgW) is
   negative. Verified by pixel measurement, not by reading the spec.

   The old rule used a fixed 800px tile. Most skeletons are far narrower
   than that, so they only ever showed a slice of one band and brightened
   then dimmed in place - the "breathing" this replaces. */
@keyframes shimmer {
  from { background-position: 150% 0; }
  to   { background-position: -50% 0; }
}
@keyframes navDrop {
  0% { opacity: 0; transform: translateY(-16px); }
  100% { opacity: 1; transform: translateY(0); }
}
@keyframes lineGlow {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(77,166,255,0.3)); }
  50% { filter: drop-shadow(0 0 18px rgba(77,166,255,0.9)); }
}
@keyframes slideFromTop { from { opacity: 0; transform: translateY(-100px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes slideFromLeft { from { opacity: 0; transform: translateX(-120px) scale(0.97); } to { opacity: 1; transform: translateX(0) scale(1); } }
@keyframes slideFromRight { from { opacity: 0; transform: translateX(120px) scale(0.97); } to { opacity: 1; transform: translateX(0) scale(1); } }
@keyframes slideFromBottom { from { opacity: 0; transform: translateY(100px) scale(0.97); } to { opacity: 1; transform: translateY(0) scale(1); } }
@keyframes pulse { 0%, 100% { opacity: 1; } 50% { opacity: 0.4; } }
@keyframes support-spin { to { transform: rotate(360deg); } }
/* index + password: 4-layer gradient drift backdrop */
@keyframes gradientDrift1 {
  0%   { transform: translate(-50%, -50%) rotate(-48deg) scale(1); }
  25%  { transform: translate(-47%, -53%) rotate(-30deg) scale(1.04); }
  50%  { transform: translate(-50%, -50%) rotate(-15deg) scale(1.06); }
  75%  { transform: translate(-53%, -47%) rotate(-35deg) scale(1.02); }
  100% { transform: translate(-50%, -50%) rotate(-48deg) scale(1); }
}
@keyframes gradientDrift2 {
  0%   { transform: translate(-50%, -50%) rotate(20deg) scale(1); }
  25%  { transform: translate(-53%, -47%) rotate(35deg) scale(1.05); }
  50%  { transform: translate(-50%, -50%) rotate(50deg) scale(1.08); }
  75%  { transform: translate(-47%, -53%) rotate(35deg) scale(1.03); }
  100% { transform: translate(-50%, -50%) rotate(20deg) scale(1); }
}
@keyframes gradientDrift3 {
  0%   { transform: translate(-50%, -50%) rotate(65deg) scale(1); opacity: 0.45; }
  25%  { transform: translate(-46%, -54%) rotate(50deg) scale(1.06); opacity: 0.5; }
  50%  { transform: translate(-50%, -50%) rotate(38deg) scale(1.1); opacity: 0.55; }
  75%  { transform: translate(-54%, -46%) rotate(52deg) scale(1.04); opacity: 0.48; }
  100% { transform: translate(-50%, -50%) rotate(65deg) scale(1); opacity: 0.45; }
}
@keyframes gradientDrift4 {
  0%   { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
  33%  { transform: translate(-48%, -52%) scale(1.1); opacity: 0.45; }
  66%  { transform: translate(-52%, -48%) scale(1.05); opacity: 0.35; }
  100% { transform: translate(-50%, -50%) scale(1); opacity: 0.3; }
}
/* index + password: one-time "spin-out big bang" hero intro.
   The four orbs are wrapped in .hero-orbs; the group starts as a tiny spinning seed and
   smoothly expands out (slight overshoot) to settle at scale 1 / rotate 0 - matching the
   product-page reveal easing & speed - at which point the orbs' own infinite drift simply
   continues. A deep-blue flash (.hero::before) and an outward JS particle blast punctuate it. */
@keyframes heroFlash {
  0%   { opacity: 0; transform: translate(-50%, -50%) scale(0.4); }
  30%  { opacity: 0.6; transform: translate(-50%, -50%) scale(1); }
  100% { opacity: 0; transform: translate(-50%, -50%) scale(1.6); }
}
/* One smooth easing (no segmented timing) for a clean, product-page-style reveal:
   a small spinning seed swings open, overshoots slightly large, then magnetically
   settles to scale 1 / rotate 0 where the orbs' own drift continues. */
@keyframes heroBang {
  0%   { opacity: 0; transform: scale(0.1) rotate(-200deg); }
  16%  { opacity: 1; }
  70%  { transform: scale(1.1) rotate(-6deg); }   /* slightly too large */
  100% { opacity: 1; transform: scale(1) rotate(0deg); }   /* magnetic size-down */
}
/* product-evolve + product-origin: shared hero animation set (GPU-accelerated) */
@keyframes heroReveal1 {
  0%   { transform: translate3d(-50%, -50%, 0) rotate(-48deg) scale(0); opacity: 0; }
  40%  { opacity: 0.85; }
  65%  { transform: translate3d(-50%, -50%, 0) rotate(-48deg) scale(1.06); }
  100% { transform: translate3d(-50%, -50%, 0) rotate(-48deg) scale(1); opacity: 0.8; }
}
@keyframes heroReveal2 {
  0%   { transform: translate3d(-50%, -50%, 0) rotate(20deg) scale(0); opacity: 0; }
  40%  { opacity: 0.5; }
  65%  { transform: translate3d(-50%, -50%, 0) rotate(20deg) scale(1.08); }
  100% { transform: translate3d(-50%, -50%, 0) rotate(20deg) scale(1); opacity: 0.5; }
}
@keyframes glowPulse {
  0%   { transform: translate3d(-50%, -50%, 0) scale(0); opacity: 0; }
  30%  { opacity: 1; }
  60%  { transform: translate3d(-50%, -50%, 0) scale(6); opacity: 0.3; }
  100% { transform: translate3d(-50%, -50%, 0) scale(8); opacity: 0; }
}
@keyframes ringExpand {
  0%   { transform: translate3d(-50%, -50%, 0) scale(0); opacity: 0; }
  15%  { opacity: 0.25; }
  100% { transform: translate3d(-50%, -50%, 0) scale(14); opacity: 0; }
}
@keyframes contentFadeIn {
  0% { opacity: 0; transform: translateY(16px); }
  100% { opacity: 1; transform: translateY(0); }
}

@keyframes p_dashboard_evolve__btn-pay-pulse {
  0%   { opacity: 0; transform: scale(0.96); }
  50%  { opacity: 0.65; }
  100% { opacity: 0; transform: scale(1.08); }
}



@keyframes p_more__demoLoaderSlide {
  0%   { transform: translateX(-100%); }
  100% { transform: translateX(350%); }
}
@property --reveal {
  syntax: '<number>';
  inherits: true;
  initial-value: 0;
}


@keyframes p_pricing__accordionFade {
  0% { opacity: 0; }
  100% { opacity: 1; }
}
@keyframes p_pricing__blink { 0%,100% { opacity: 0.7; } 50% { opacity: 0; } }
@keyframes p_pricing__lineGlowLight {
  0%, 100% { filter: drop-shadow(0 0 6px rgba(43,127,212,0.25)); }
  50% { filter: drop-shadow(0 0 18px rgba(43,127,212,0.6)); }
}




@keyframes p_schedule__processStagger {
  to { opacity: 1; transform: translateY(0); }
}
@keyframes llRequiredPunchIn { 0%, 100% { color: #ff5050; transform: scale(1); } }
@keyframes llBtnShake {
  0% { transform: translateX(0); }
  20% { transform: translateX(-3px); }
  40% { transform: translateX(3px); }
  60% { transform: translateX(-2px); }
  80% { transform: translateX(1px); }
  100% { transform: translateX(0); }
}
@keyframes llTimerFill { to { width: 100%; } }
@media (prefers-reduced-motion: reduce) {
  /* No animation: --reveal is pinned to 1 by JS for reduce-motion users,
     slot renders at its final size immediately. */
}

/* ─── Wrong-password feedback ───
   Mirrors the cs submit-button shake (btnShake on the submission form):
   same 0.5s ease-in-out translateX pattern, applied to the gate card. */
@keyframes pwGateShake {
  0%       { transform: scale(1) translateX(0); }
  20%      { transform: scale(1) translateX(-6px); }
  40%      { transform: scale(1) translateX(6px); }
  60%      { transform: scale(1) translateX(-4px); }
  80%      { transform: scale(1) translateX(2px); }
  100%     { transform: scale(1) translateX(0); }
}
@keyframes supportRequiredPunch { 0%, 100% { color: #ff5050; transform: scale(1); } }

@keyframes supportBtnShake { 0% { transform: translateX(0); } 20% { transform: translateX(-3px); } 40% { transform: translateX(3px); } 60% { transform: translateX(-2px); } 80% { transform: translateX(1px); } 100% { transform: translateX(0); } }
@keyframes supportTimerFill { to { width: 100%; } }
@keyframes supportReturnToBtn { from { background: transparent; border-color: rgba(255,100,100,0.3); } to { background: #fff; border-color: transparent; } }
@keyframes csRingPulse { 0% { transform: scale(0.6); opacity: 0; } 28% { opacity: 1; } 100% { transform: scale(1.7); opacity: 0; } }
@keyframes csDraw { to { stroke-dashoffset: 0; } }

/* Staggered rise-in for the success copy */
@keyframes csRise { from { opacity: 0; transform: translateY(12px); } to { opacity: 1; transform: translateY(0); } }

/* ── The brand-mark success animation (2026-08-18) ──
   Replaces the drawn tick everywhere a submit succeeds. The primaryLogo
   4-square mark pops in, the squares fragment outward (per-square --fx/--fy/
   --fr set where the component is styled), hold and shake, then spring back
   with a small inward overshoot, holding the exact brand colours throughout
   (the hue-rotate colour sweep was removed 2026-08-22 at Ellis's ask, here
   and in both synced copies). llmWindow is the logo file's own amber
   flicker, resumed once the mark has settled. The same keyframes are copied inline into the submit
   subsite and the dev portal (self-contained pages - keep all three in sync). */
@keyframes llmPop {
  0% { transform: scale(0.55); opacity: 0; }
  18% { opacity: 1; }
  32% { transform: scale(1.06); }
  50%, 100% { transform: scale(1); opacity: 1; }
}
@keyframes llmFrag {
  0%, 14% { transform: translate(0, 0) rotate(0deg); }
  38%, 60% { transform: translate(var(--fx), var(--fy)) rotate(var(--fr)); }
  82% { transform: translate(calc(var(--fx) / -14), calc(var(--fy) / -14)) rotate(calc(var(--fr) / -6)); }
  100% { transform: translate(0, 0) rotate(0deg); }
}
@keyframes llmShake {
  0%, 36% { transform: translate(0, 0) rotate(0deg); }
  41% { transform: translate(-1px, 0.6px) rotate(-1.4deg); }
  46% { transform: translate(1.1px, -0.5px) rotate(1.2deg); }
  51% { transform: translate(-1.1px, -0.5px) rotate(-1deg); }
  56% { transform: translate(1px, 0.5px) rotate(0.8deg); }
  60% { transform: translate(-0.6px, 0.3px) rotate(-0.4deg); }
  64%, 100% { transform: translate(0, 0) rotate(0deg); }
}
/* 94.5% closes the second blink. With steps(1) each interval holds its START value, so without it 93% to 100% held 0.25 for the last 0.63 s of every cycle and the amber window read dark olive (2026-09-04). Same fix in every synced copy. */
/* Fill, not opacity, since 2026-09-11: the dim is the 25% blend on the tile
   ground (--llm-dim, the light ground's #f7e9d0 where a page sets it). */
@keyframes llmWindow { 0%, 88%, 100% { fill: #f5b342; } 90%, 93% { fill: var(--llm-dim, #433926); } 91.5%, 94.5% { fill: #f5b342; } }
@keyframes supportReturnToBtnLight { from { background: transparent; border-color: rgba(200,50,50,0.3); } to { background: #1a1a2e; border-color: transparent; } }

/* ============================================================
   Header nav - non-active items are centred circles on mobile
   ------------------------------------------------------------
   The active page keeps its text label and the existing
   collapse→expand morph (so you can read which page you're on,
   and the indicator extends from a circle into the labelled pill
   when the destination page loads). The only fix here is that
   non-active links are icon-only on mobile, so their hover/tap
   highlight must be a CENTRED CIRCLE - symmetric padding + no gap
   - rather than the off-centre pill the asymmetric padding gave.
   ============================================================ */
@media (max-width: 640px) {
  body[class*="page-"] .nav a:not(.active) { padding: 9px; gap: 0; }
}

/* Brand mark in the nav (Ellis, 2026-08-18): primaryLogo.svg replaces the
   Home link's house icon. Unlike the other nav icons it stays visible on
   desktop too, so the mark sits at the left end of the nav pill site-wide;
   on mobile it takes the house icon's 16px slot and rides the existing
   collapse/expand morph untouched. Served as an img element so the SVG's
   own window-flicker style block stays scoped inside the file instead of
   leaking into the page. The selector deliberately outranks every per-page
   `body.page-X .nav-icon { display: none }` desktop rule. */
body[class*="page-"] .nav a .nav-logo { display: block; width: 18px; height: 18px; }
@media (max-width: 640px) {
  body[class*="page-"] .nav a .nav-logo { width: 16px; height: 16px; }
}

/* ── The header slot's sign-in link (2026-09-17) ──
   The slot the light/dark toggle used to sit in is the client portal sign-in
   link now (the theme is chosen in the footer's Preferences overlay,
   script.js section 7). It keeps the slot's own classes, so every page's
   `.theme-toggle` and `.theme-toggle-mobile` rules still draw it, and the
   colours it takes from `.nav a` are the ones the toggle had. What `.nav a`
   would otherwise force on it is the pill geometry, which these rules put
   back: a plain `body.page-X .theme-toggle` rule loses to `body.page-X .nav a`
   on any property both set. */
html body[class*="page-"] .nav a.nav-signin {
  width: 34px; height: 34px; min-width: 34px; margin-left: 2px;
  padding: 0; gap: 0; font-size: 0; border-radius: 50%;
  text-shadow: none; flex-shrink: 0;
}
html body[class*="page-"] .nav a.nav-signin.active { background: transparent; }
.nav-signin { text-decoration: none; }
.nav-signin .nav-signin-icon { display: block; width: 16px; height: 16px; flex-shrink: 0; }

/* ── The brand mark (2026-09-11) ──
   One element, two background layers, and only ever one of them drawn: the
   lit file at full size over the dim twin at 0x0. A dip swaps the two sizes,
   so the swap repaints the whole mark at once and nothing animates inside an
   image. The flicker used to live inside primaryLogo.svg, and Ellis kept
   seeing the window dimmed with one pixel still lit; the static pair is
   built by Agency/scripts/markFiles.py. inline-block with a 1:1 ratio sits
   exactly where the old inline img did, so each page's width rule sizes it
   unchanged. The white pair only under this site's own light theme. */
.logo-mark {
  display: inline-block; aspect-ratio: 1 / 1; flex: 0 0 auto;
  background: url('../assets/logos/primaryLogoLit.svg') 0 0 / 100% 100% no-repeat,
              url('../assets/logos/primaryLogoDim.svg') 0 0 / 0 0 no-repeat;
  animation: llMarkWindow 9s steps(1, end) infinite;
}
[data-theme="light"] .logo-mark {
  background-image: url('../assets/logos/primaryLogoLightLit.svg'), url('../assets/logos/primaryLogoLightDim.svg');
}
.footer-logo.logo-mark { width: 48px; }
@keyframes llMarkWindow {
  0%, 88%, 100% { background-size: 100% 100%, 0 0; }
  90%, 93% { background-size: 0 0, 100% 100%; }
  91.5%, 94.5% { background-size: 100% 100%, 0 0; }
}
@media (prefers-reduced-motion: reduce) { .logo-mark { animation: none; } }
@keyframes umBlink { 0%, 55% { opacity: 0.8; } 55.01%, 100% { opacity: 0; } }

/* ============================================================
   Text inflation lock (Ellis, 2026-08-20).
   Mobile Safari and Chrome-Android run their own text-inflation pass and
   recompute the multiplier from the viewport width. Rotating a phone to
   landscape raises it; rotating back does NOT reliably lower it again, so
   the inflated size sticks and every text-heavy block keeps the landscape
   sizing in portrait. It bit the home testimonial cards hardest (the
   longest repeated text blocks on the site) but the mechanism is
   site-wide, and it is the UA doing it - nothing in our own CSS/JS moves:
   measured 390x844 -> 844x390 -> 390x844 in a desktop engine and the
   cards come back byte-identical (342x272/297/307/297, quote 15px).
   100% - not none - keeps pinch-zoom and the OS text-size setting working.
   Inherited from html, so it covers all 20 pages via this one rule.
   ============================================================ */
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }

/* ============================================================
   Custom scrollbar - brand amber (Ellis, 2026-08-20; was light blue).
   Applies site-wide. Component-scoped scrollbars (e.g. .content-file-list,
   #imagesFileList) are MORE specific so they keep their own bespoke styling.
   Slimmed from a 12px gutter to 9px: the transparent border is inset with
   background-clip so the visible thumb goes 6px -> 5px at rest and 8px -> 7px
   under the pointer, which keeps the same grow-on-hover cue at a lighter
   weight. Colours are the estate's two ambers - #f5b342 on the dark default,
   the fully-saturated #f0a500 on [data-theme="light"] where the pale one
   washes out against #eef1f6. Both webkit forms are listed because the
   VIEWPORT scrollbar belongs to <html> itself, so the descendant selector
   alone would only reach nested scrollers.

   Two engine paths, deliberately kept to the same colour. Chrome 121+ and
   Firefox take the STANDARD scrollbar-width / scrollbar-color below and
   ignore the ::-webkit- block entirely, so `thin` is what sets the width
   there - already the slim option, and there is nothing narrower short of
   hiding it. Safari (and anything pre-121) takes the ::-webkit- block, where
   9px is the change from the old 12px. The standard colour is the solid
   #f5b342 rather than the old blue's 0.6 alpha so both paths render the same
   brand yellow instead of one washed-out and one full-strength.
   ============================================================ */
html { scrollbar-color: #f5b342 transparent; scrollbar-width: thin; }
html[data-theme="light"] { scrollbar-color: #f0a500 transparent; }
::-webkit-scrollbar { width: 9px; height: 9px; }
::-webkit-scrollbar-track { background: transparent; }
::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, #f5b342, #f0a500);
  border-radius: 999px;
  border: 2px solid transparent;
  background-clip: padding-box;
}
::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, #ffd98c, #f5b342);
  border: 1px solid transparent;
  background-clip: padding-box;
}
:root[data-theme="light"]::-webkit-scrollbar-thumb,
:root[data-theme="light"] ::-webkit-scrollbar-thumb {
  background: linear-gradient(180deg, #f0a500, #d99400);
  background-clip: padding-box;
}
:root[data-theme="light"]::-webkit-scrollbar-thumb:hover,
:root[data-theme="light"] ::-webkit-scrollbar-thumb:hover {
  background: linear-gradient(180deg, #f5b342, #f0a500);
  background-clip: padding-box;
}
::-webkit-scrollbar-corner { background: transparent; }

/* ============================================================
   Cookie consent banner (injected by script.js on every page).
   Blurred glass card with a faint light-blue grid - the same surface
   language as the password gate / content overlay. Bottom-anchored,
   slides up on .ll-cookie-open. Equal-prominence Accept/Reject buttons.
   ============================================================ */
/* Text colours are scoped to the component: --text-primary / --text-secondary
   are NOT declared globally, so without this the banner/overlay text fell back to
   whatever colour it happened to inherit (non-deterministic, and it collapsed the
   primary/secondary hierarchy). Default = the dark glass surface (light text);
   the light-theme override sits with the other [data-theme="light"] cookie rules. */
.ll-cookie, .ll-cookie-overlay {
  --text-primary: rgba(255,255,255,0.92);
  --text-secondary: rgba(255,255,255,0.72);   /* WCAG AA: lifted from 0.62 for the 13px-bold quiet buttons + descriptions */
}
.ll-cookie {
  position: fixed; left: 0; right: 0; bottom: 0; z-index: 99999;
  display: flex; justify-content: center; padding: 16px;
  pointer-events: none;
}
.ll-cookie-card {
  position: relative; overflow: hidden; pointer-events: auto;
  width: 100%; max-width: 640px;
  display: flex; align-items: center; gap: 20px;
  padding: 18px 20px; border-radius: 14px;
  background: linear-gradient(135deg, rgba(255,255,255,0.07) 0%, rgba(255,255,255,0.02) 50%, rgba(255,255,255,0.05) 100%);
  border: 1px solid rgba(255,255,255,0.12);
  backdrop-filter: blur(24px) saturate(1.6);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  box-shadow: 0 24px 64px rgba(0,0,0,0.5), 0 0 32px rgba(77,166,255,0.08), inset 0 1px 0 rgba(255,255,255,0.08);
  transform: translateY(140%);
  opacity: 0;
  transition: transform 0.5s cubic-bezier(0.22,1,0.36,1), opacity 0.4s ease;
}
.ll-cookie.ll-cookie-open .ll-cookie-card { transform: translateY(0); opacity: 1; }
/* Faint SVG grid over the glass - matches the content overlay / password gate. */
.ll-cookie-card::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image:
    url("data:image/svg+xml,%3Csvg width='160' height='160' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='160' height='160' fill='none'/%3E%3Cline x1='40' y1='0' x2='40' y2='160' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='80' y1='0' x2='80' y2='160' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='120' y1='0' x2='120' y2='160' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='0' y1='40' x2='160' y2='40' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='0' y1='80' x2='160' y2='80' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='0' y1='120' x2='160' y2='120' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}
.ll-cookie-body { position: relative; z-index: 1; flex: 1 1 auto; min-width: 0; }
.ll-cookie-title {
  font-size: 13px; font-weight: 700; letter-spacing: 0.02em;
  color: var(--text-primary); margin-bottom: 5px;
}
.ll-cookie-prompt { color: var(--blue-light); font-weight: 800; margin-right: 4px; }
.ll-cookie-text { font-size: 12.5px; line-height: 1.55; color: var(--text-secondary); margin: 0; }
.ll-cookie-link { color: var(--blue-light); text-decoration: none; }
.ll-cookie-link:hover { text-decoration: underline; }
.ll-cookie-actions { position: relative; z-index: 1; display: flex; gap: 10px; flex: 0 0 auto; }
.ll-cookie-btn {
  font-family: inherit; font-size: 13px; font-weight: 700; letter-spacing: 0.01em;
  padding: 11px 18px; border-radius: 10px; cursor: pointer;
  min-height: 44px; white-space: nowrap;
  transition: transform 0.2s ease, background 0.2s ease, box-shadow 0.2s ease, border-color 0.2s ease;
}
/* The three actions reuse the site's CTA language (hero .btn-primary /
   .btn-outline), recoloured only - shape/size are unchanged.
   Accept = primary (solid), Reject = secondary (outline), Options = tertiary. */
/* Options - tertiary: same outline family as Reject but quieter (fainter border
   + muted text) so the three tie together without competing. */
.ll-cookie-options {
  background: transparent; color: var(--text-secondary);
  border: 1px solid rgba(255,255,255,0.1);
}
.ll-cookie-options:hover { background: rgba(255,255,255,0.05); color: var(--text-primary); border-color: rgba(255,255,255,0.4); transform: translateY(-1px); }
/* Reject - mirrors the secondary CTA (.btn-outline): transparent + border. */
.ll-cookie-reject {
  background: transparent; color: var(--text-primary);
  border: 1px solid rgba(255,255,255,0.2);
}
.ll-cookie-reject:hover { background: rgba(255,255,255,0.05); border-color: rgba(255,255,255,0.7); transform: translateY(-1px); }
/* Accept - mirrors the primary CTA (.btn-primary): solid white, dark text. */
.ll-cookie-accept {
  background: #fff; color: var(--bg, #06121f); border: none; box-shadow: none;
}
.ll-cookie-accept:hover { background: rgba(255,255,255,0.9); transform: translateY(-1px); box-shadow: 0 8px 30px rgba(77,166,255,0.25); }
/* Footer "Cookie Preferences" - styled to sit alongside the other footer links. */
.ll-cookie-prefs-link {
  background: none; border: none; padding: 0; cursor: pointer;
  font: inherit; text-decoration: none;
}
[data-theme="light"] .ll-cookie, [data-theme="light"] .ll-cookie-overlay {
  --text-primary: #1a1a2e;
  --text-secondary: rgba(0,0,0,0.72);   /* WCAG AA: lifted from 0.6 (see dark-theme note above) */
}
[data-theme="light"] .ll-cookie-card {
  background: linear-gradient(135deg, rgba(255,255,255,0.72) 0%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0.62) 100%);
  border-color: rgba(0,0,0,0.08);
  box-shadow: 0 24px 64px rgba(10,30,61,0.18), 0 0 32px rgba(77,166,255,0.06), inset 0 1px 0 rgba(255,255,255,0.6);
}
[data-theme="light"] .ll-cookie-reject { background: transparent; color: #1a1a2e; border-color: rgba(0,0,0,0.15); }
[data-theme="light"] .ll-cookie-reject:hover { background: rgba(0,0,0,0.04); border-color: rgba(0,0,0,0.6); }
[data-theme="light"] .ll-cookie-accept { background: #1a1a2e; color: #fff; }
[data-theme="light"] .ll-cookie-accept:hover { background: #2a2a4e; box-shadow: 0 8px 30px rgba(43,127,212,0.2); }
@media (max-width: 540px) {
  /* Lift the bar clear of the mobile browser's bottom toolbar / gesture area so
     it's never clipped by the viewport - kept as low as possible (bottom-anchored),
     just nudged up. env(safe-area-inset-bottom) adds the home-indicator inset on
     notched devices (0 where unsupported, so harmless). KEEP IN SYNC with the
     0,2,x override near the end of this file. */
  .ll-cookie { padding: 10px 10px calc(20px + env(safe-area-inset-bottom, 0px)); }
  .ll-cookie-card { flex-direction: column; align-items: stretch; gap: 14px; padding: 18px; }
  .ll-cookie-actions { width: 100%; }
  .ll-cookie-btn { flex: 1 1 0; }
}
@media (prefers-reduced-motion: reduce) {
  .ll-cookie-card { transition: opacity 0.3s ease; transform: none; }
  .ll-cookie.ll-cookie-open .ll-cookie-card { transform: none; }
}

/* ============================================================
   Cookie "Options" overlay - full-page granular consent settings.
   Same faint light-blue 160px grid + blurred glass language as the
   password gate / content overlay. Essential is locked on; Analytics is
   a switch; Save / Accept all / Reject all write consent + persist.
   ============================================================ */
.ll-cookie-overlay {
  position: fixed; inset: 0; z-index: 100000;
  display: flex; align-items: center; justify-content: center; padding: 24px;
  background: rgba(5,10,20,0.72);
  backdrop-filter: blur(20px) saturate(1.4);
  -webkit-backdrop-filter: blur(20px) saturate(1.4);
  opacity: 0; visibility: hidden; pointer-events: none;
  transition: opacity 0.35s ease, visibility 0s linear 0.35s;
}
.ll-cookie-overlay.ll-cookie-overlay-open {
  opacity: 1; visibility: visible; pointer-events: auto;
  transition: opacity 0.35s ease, visibility 0s linear 0s;
}
/* Faint full-page SVG grid behind the glass panel. */
.ll-cookie-overlay::before {
  content: ''; position: absolute; inset: 0; pointer-events: none;
  background-image:
    url("data:image/svg+xml,%3Csvg width='160' height='160' xmlns='http://www.w3.org/2000/svg'%3E%3Crect width='160' height='160' fill='none'/%3E%3Cline x1='40' y1='0' x2='40' y2='160' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='80' y1='0' x2='80' y2='160' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='120' y1='0' x2='120' y2='160' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='0' y1='40' x2='160' y2='40' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='0' y1='80' x2='160' y2='80' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3Cline x1='0' y1='120' x2='160' y2='120' stroke='rgba(77,166,255,0.04)' stroke-width='1'/%3E%3C/svg%3E");
  background-size: 160px 160px;
}
.ll-cookie-panel {
  position: relative; width: 100%; max-width: 460px;
  /* Scroll fallback: with 5 rows the panel can exceed a short/phone viewport while
     background scroll is locked - cap its height and scroll internally so Reject/
     Save/Accept stay reachable. No visual change when it fits. */
  max-height: calc(100vh - 48px); max-height: calc(100dvh - 48px); overflow-y: auto;
  padding: 28px 26px 24px; border-radius: 16px;
  background: linear-gradient(135deg, rgba(255,255,255,0.07) 0%, rgba(255,255,255,0.02) 50%, rgba(255,255,255,0.05) 100%);
  border: 1px solid rgba(255,255,255,0.12);
  backdrop-filter: blur(24px) saturate(1.6);
  -webkit-backdrop-filter: blur(24px) saturate(1.6);
  box-shadow: 0 24px 64px rgba(0,0,0,0.5), 0 0 32px rgba(77,166,255,0.08), inset 0 1px 0 rgba(255,255,255,0.08);
  transform: scale(0.96); opacity: 0;
  transition: transform 0.4s cubic-bezier(0.22,1,0.36,1), opacity 0.3s ease;
}
.ll-cookie-overlay.ll-cookie-overlay-open .ll-cookie-panel { transform: scale(1); opacity: 1; }
.ll-cookie-overlay-title { font-size: 15px; margin-bottom: 0; }
.ll-cookie-overlay-title .ll-cookie-prompt { margin-right: 0.31em; }
.ll-cookie-overlay-sub { font-size: 12.5px; line-height: 1.55; color: var(--text-secondary); margin: 8px 0 0; }
.ll-cookie-opts { display: flex; flex-direction: column; gap: 12px; margin: 20px 0 22px; }
.ll-cookie-opt {
  display: flex; align-items: center; gap: 16px;
  padding: 14px 16px; border-radius: 10px;
  background: rgba(255,255,255,0.03); border: 1px solid rgba(255,255,255,0.08);
}
.ll-cookie-opt-text { flex: 1 1 auto; min-width: 0; }
.ll-cookie-opt-name { font-size: 13px; font-weight: 700; color: var(--text-primary); margin-bottom: 3px; }
.ll-cookie-opt-desc { font-size: 12px; line-height: 1.5; color: var(--text-secondary); }
/* Switch (pill toggle) - role="switch", driven by aria-checked. */
.ll-cookie-switch {
  position: relative; flex: 0 0 auto; width: 46px; height: 28px;
  padding: 0; border: none; background: none; cursor: pointer;
  -webkit-tap-highlight-color: transparent;
}
.ll-cookie-switch-track {
  display: block; width: 46px; height: 28px; border-radius: 999px;
  background: rgba(255,255,255,0.12); border: 1px solid rgba(255,255,255,0.18);
  transition: background 0.25s ease, border-color 0.25s ease;
}
.ll-cookie-switch-thumb {
  position: absolute; top: 4px; left: 4px; width: 20px; height: 20px; border-radius: 50%;
  background: #fff; box-shadow: 0 2px 6px rgba(0,0,0,0.35);
  transition: transform 0.25s cubic-bezier(0.22,1,0.36,1);
}
.ll-cookie-switch[aria-checked="true"] .ll-cookie-switch-track { background: linear-gradient(135deg, #4da6ff, #2b8ae6); border-color: transparent; }
.ll-cookie-switch[aria-checked="true"] .ll-cookie-switch-thumb { transform: translateX(18px); }
.ll-cookie-switch[disabled] { cursor: default; opacity: 0.7; }
.ll-cookie-switch:focus-visible { outline: 2px solid var(--blue-light); outline-offset: 3px; border-radius: 999px; }
.ll-cookie-overlay-actions { display: flex; gap: 10px; }
.ll-cookie-overlay-actions .ll-cookie-btn { flex: 1 1 0; padding: 11px 12px; }
/* Save choices - same quiet tertiary as Options. */
.ll-cookie-save { background: transparent; color: var(--text-secondary); border: 1px solid rgba(255,255,255,0.1); }
.ll-cookie-save:hover { background: rgba(255,255,255,0.05); color: var(--text-primary); border-color: rgba(255,255,255,0.4); transform: translateY(-1px); }
/* Close button - mirrors the password-gate close. */
.ll-cookie-overlay-close {
  position: absolute; top: 12px; right: 12px; width: 32px; height: 32px;
  display: flex; align-items: center; justify-content: center; padding: 0;
  border-radius: 8px; border: 1px solid rgba(255,255,255,0.1);
  background: linear-gradient(135deg, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0.02) 50%, rgba(255,255,255,0.04) 100%);
  color: rgba(255,255,255,0.55); cursor: pointer;
  transition: color 0.2s, border-color 0.2s, background 0.2s, transform 0.2s;
  -webkit-tap-highlight-color: transparent;
}
.ll-cookie-overlay-close:hover { color: #fff; border-color: rgba(255,255,255,0.2); background: linear-gradient(135deg, rgba(255,255,255,0.10) 0%, rgba(255,255,255,0.04) 50%, rgba(255,255,255,0.08) 100%); }
.ll-cookie-overlay-close:active { transform: scale(0.94); }
.ll-cookie-overlay-close:focus { outline: none; }
.ll-cookie-overlay-close:focus-visible { outline: 2px solid var(--blue-light); outline-offset: 2px; }
.ll-cookie-overlay-close svg { width: 14px; height: 14px; }
html.ll-cookie-noscroll, html.ll-cookie-noscroll body { overflow: hidden; }
/* Light theme */
[data-theme="light"] .ll-cookie-overlay { background: rgba(238,241,246,0.78); }
[data-theme="light"] .ll-cookie-panel {
  background: linear-gradient(135deg, rgba(255,255,255,0.72) 0%, rgba(255,255,255,0.5) 50%, rgba(255,255,255,0.62) 100%);
  border-color: rgba(0,0,0,0.08);
  box-shadow: 0 24px 64px rgba(10,30,61,0.18), 0 0 32px rgba(77,166,255,0.06), inset 0 1px 0 rgba(255,255,255,0.6);
}
[data-theme="light"] .ll-cookie-opt { background: rgba(0,0,0,0.025); border-color: rgba(0,0,0,0.08); }
[data-theme="light"] .ll-cookie-switch-track { background: rgba(0,0,0,0.12); border-color: rgba(0,0,0,0.14); }
[data-theme="light"] .ll-cookie-options, [data-theme="light"] .ll-cookie-save { color: rgba(0,0,0,0.72); border-color: rgba(0,0,0,0.1); }   /* WCAG AA: lifted from 0.5 (was ~3.9:1) */
[data-theme="light"] .ll-cookie-options:hover, [data-theme="light"] .ll-cookie-save:hover { background: rgba(0,0,0,0.04); color: #1a1a2e; border-color: rgba(0,0,0,0.3); }

[data-theme="light"] .ll-cookie-overlay-close {
  background: linear-gradient(135deg, rgba(255,255,255,0.7) 0%, rgba(255,255,255,0.5) 100%);
  border-color: rgba(0,0,0,0.1); color: rgba(0,0,0,0.5);
}
[data-theme="light"] .ll-cookie-overlay-close:hover { background: linear-gradient(135deg, rgba(255,255,255,0.85) 0%, rgba(255,255,255,0.65) 100%); border-color: rgba(0,0,0,0.2); color: #1a1a2e; }
@media (max-width: 540px) {
  .ll-cookie-overlay { padding: 16px; }
  .ll-cookie-panel { padding: 24px 20px 20px; }
  .ll-cookie-overlay-actions { flex-direction: column; }
}
@media (prefers-reduced-motion: reduce) {
  .ll-cookie-overlay { transition: opacity 0.2s ease; }
  .ll-cookie-panel { transition: opacity 0.2s ease; transform: none; }
  .ll-cookie-overlay.ll-cookie-overlay-open .ll-cookie-panel { transform: none; }
  .ll-cookie-switch-thumb { transition: none; }
}

/* ── Autofill keeps the brand surfaces ──
   Chrome/Safari paint their own pale yellow/blue fill and near-black text
   over autofilled fields, which breaks the glass inputs in both themes.
   Clipping that imposed background to the glyphs hides it behind the text
   paint, so the field keeps its own translucent surface, and the text/caret
   colours are pinned per theme so the value is always readable. */
input:-webkit-autofill,
input:-webkit-autofill:hover,
input:-webkit-autofill:focus,
textarea:-webkit-autofill,
select:-webkit-autofill {
  -webkit-background-clip: text;
  -webkit-text-fill-color: #fff;
  caret-color: #fff;
}
[data-theme="light"] input:-webkit-autofill,
[data-theme="light"] input:-webkit-autofill:hover,
[data-theme="light"] input:-webkit-autofill:focus,
[data-theme="light"] textarea:-webkit-autofill,
[data-theme="light"] select:-webkit-autofill {
  -webkit-text-fill-color: #1a1a2e;
  caret-color: #1a1a2e;
}
/* Firefox exposes the standard pseudo-class instead; kept as its own block
   because an unrecognised selector would drop the whole rule elsewhere. */
input:autofill { -webkit-text-fill-color: #fff; caret-color: #fff; }
[data-theme="light"] input:autofill { -webkit-text-fill-color: #1a1a2e; caret-color: #1a1a2e; }

/* ── Survive the per-page box reset (main site only) ──
   Every main-site page carries `body.page-X *, body.page-X *::before, ... {
   margin:0; padding:0 }` in its page-*.css (specificity 0,1,1), which loads
   AFTER shared.css and outranks the single-class cookie rules above (0,1,0) -
   stripping the banner/overlay padding + margins (the radius/colour survive,
   so it looks "unpadded"). Re-assert the box model here at specificity 0,2,x
   so it wins on specificity alone (no !important, order-independent). The cs
   subsite uses a plain `*` reset (0,0,0) and keeps its padding directly, so it
   never needs this. KEEP THESE VALUES IN SYNC with the base rules above. */
.ll-cookie.ll-cookie { padding: 16px; }
.ll-cookie .ll-cookie-card { padding: 18px 20px; }
.ll-cookie .ll-cookie-title { margin-bottom: 5px; }
.ll-cookie .ll-cookie-prompt { margin-right: 4px; }
.ll-cookie .ll-cookie-btn { padding: 11px 18px; }
.ll-cookie-overlay.ll-cookie-overlay { padding: 24px; }
.ll-cookie-overlay .ll-cookie-panel { padding: 28px 26px 24px; }
.ll-cookie-overlay .ll-cookie-overlay-sub { margin: 8px 0 0; }
.ll-cookie-overlay .ll-cookie-opts { margin: 20px 0 22px; }
.ll-cookie-overlay .ll-cookie-opt { padding: 14px 16px; }
.ll-cookie-overlay .ll-cookie-opt-name { margin-bottom: 3px; }
.ll-cookie-overlay .ll-cookie-overlay-actions .ll-cookie-btn { padding: 11px 12px; }
@media (max-width: 540px) {
  /* Bottom lift mirrors the base rule above (kept clear of the mobile browser
     chrome + home-indicator inset). KEEP THESE VALUES IN SYNC. */
  .ll-cookie.ll-cookie { padding: 10px 10px calc(20px + env(safe-area-inset-bottom, 0px)); }
  .ll-cookie .ll-cookie-card { padding: 18px; }
  .ll-cookie-overlay.ll-cookie-overlay { padding: 16px; }
  .ll-cookie-overlay .ll-cookie-panel { padding: 24px 20px 20px; }
}

/* ── Skeleton loaders: ONE definition for the whole site (2026-07-27).
   This used to be copy-pasted into all twelve page-*.css files (base rule
   + a [data-theme="light"] twin each), which is how the two drifted apart.
   Only the SHIMMER lives here; per-page size/shape modifiers
   (.skeleton-text, .skeleton-heading, .skeleton-card, ...) stay page-scoped.

   Themes vary the two custom properties ONLY. Never re-set the `background`
   SHORTHAND in a theme override: it resets background-size to auto and
   silently kills the sweep.

   .sk-mask is the text-preserving variant - the skeleton block is a copy of
   the real markup, so it inherits the real font, wrapping, padding and
   margins and is 1:1 with the real element at every viewport by
   construction. box-decoration-break:clone gives each wrapped line its own
   shimmer box. ── */
:root                { --skel-base: rgba(255,255,255,0.04); --skel-hi: rgba(255,255,255,0.10); }
[data-theme="light"] { --skel-base: rgba(0,0,0,0.035);      --skel-hi: rgba(0,0,0,0.08); }

.skeleton,
.skeleton-content .sk-mask,
.skeleton-content .sk-btn-mask {
  background-image: linear-gradient(90deg, var(--skel-base) 25%, var(--skel-hi) 50%, var(--skel-base) 75%);
  background-size: 200% 100%;
  background-repeat: no-repeat;
  animation: shimmer 1.6s linear infinite;
}
.skeleton,
.skeleton-content .sk-mask { border-radius: 6px; }
.skeleton { pointer-events: none; }
.skeleton-content .sk-mask,
.skeleton-content .sk-btn-mask {
  color: transparent;
  -webkit-text-fill-color: transparent;
  user-select: none;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
/* A whole control masked in place. Deliberately sets NO border-radius, so the
   control's own radius still applies (a .btn stays a 999px pill); the
   page-scoped rule out-specifies this one. `inherit` would be wrong here - it
   would take the parent's radius, squaring off every pill. */
.skeleton-content .sk-btn-mask {
  border-color: transparent;
  box-shadow: none;
}
/* Skeleton copies are aria-hidden decoration - never focusable, never
   interactive, and any cloned control is inert. */
.skeleton-content button,
.skeleton-content a,
.skeleton-content input,
.skeleton-content select,
.skeleton-content textarea { pointer-events: none; }

/* ── Pixel accent (Ellis, 2026-07-21): Silkscreen is used ONLY on the
   "//" top-subtitle labels (the code-comment eyebrows above headings).
   Verified complete set: .page-label (legal/system pages), .section-label
   (index/pricing/product), .stats-label (index), .label (more/schedule/
   support), .error-label (404/cart). The testimonials' "// origin /
   // evolve" tier tags (.testimonial-package) were removed 2026-08-18 at
   Ellis's ask. Deliberately EXCLUDED, though their text also starts with
   "//": .code-detail (pricing card details), .cs-label (support form
   status), .um-note (upload meters). Nothing else may use Silkscreen. ── */
.page-label,
.section-label,
.stats-label,
.label,
.error-label {
  font-family: 'Silkscreen', monospace;
}

/* ── Brand amber (Ellis, 2026-08-18): the lit window of the primaryLogo mark
   (#f5b342) sprinkled as the estate's "live signal" - the Silkscreen "//"
   eyebrow labels below, text selection here, and the bar/pill accents
   (more's Demo tags + In-the-lab badges + demo loading bar, schedule's
   why-card tags (its accent lines went BACK to blue 2026-08-20),
   Evolve's Recommended badge, the amber
   hero orb and product-hero glow). The terminal carets on pricing/more and
   the cs/portal um-cursors went amber briefly but were put BACK to blue
   the same day at Ellis's ask - consoles keep the blue caret. Keep it to
   these small accents; the working palette stays blue. ── */

/* The "//" eyebrow labels carry the amber site-wide (same-day follow-up:
   selection + carets alone were invisible until interacted with). Same
   class set as the Silkscreen rule above. !important is deliberate: every
   page CSS colours these blue with higher-specificity rules, and this one
   commented place overrides them all rather than editing ~20 scattered
   rules. Light theme now uses the darker #8f6400 (Ellis, 2026-08-21 site
   audit): it keeps the amber hue while clearing WCAG AA at about 4.6:1 on
   the light ground. This supersedes the 2026-08-18 vividness-first
   #f0a500, which measured 1.84:1. Dark theme keeps the vivid #f5b342
   (10.8:1 there, no change needed). */
.page-label,
.section-label,
.stats-label,
.label,
.error-label {
  color: #f5b342 !important;
}
[data-theme="light"] .page-label,
[data-theme="light"] .section-label,
[data-theme="light"] .stats-label,
[data-theme="light"] .label,
[data-theme="light"] .error-label {
  color: #8f6400 !important;
}
::selection { background: rgba(245,179,66,0.32); color: #fff; }
[data-theme="light"] ::selection { background: rgba(245,179,66,0.5); color: #1a1a2e; }
