/* =============================================================================
   Kazi Couriers, site styles
   ---------------------------------------------------------------------------
   - Tokens for light + dark modes via CSS variables
   - prefers-color-scheme: dark respected automatically
   - WCAG 2.1 AA focus-visible styling for every interactive element
   - prefers-reduced-motion respected
   - Print stylesheet for legal pages
   - Skip-to-content link styling
   ========================================================================= */

/* ---------- DESIGN TOKENS ---------- */

/* 🔴 DARK MODE IS OFF, DELIBERATELY.
   Owner, 2026-08-26: "our colour scheme is now orange and white" and
   "theres just too much black on the screen". The site used to follow the OS
   preference and invert every surface to near-black, which is exactly what he
   was looking at when he said that.
   The dark rules below are KEPT, not deleted. `kazi-dark-enabled` is not a
   real media feature, so the whole query never matches and the block is inert.
   To bring dark mode back, delete " and (kazi-dark-enabled: yes)" from each
   @media line and remove the same guard from the <source media=...> in the
   page headers. */
:root {
  color-scheme: light;
  /* Brand tokens, constant across themes */
  --brand-orange: #FF5A1F;
  --brand-orange-hover: #FF8A3D;
  --brand-orange-ink: #C7400E;
  /* Brightest same-hue orange that still clears WCAG AA (4.55:1)
     with white text. #FF5A1F itself is only 3.12:1. */
  --brand-orange-btn-hover: #CF4919;
  --brand-orange-deep: #9A3412;
  --brand-orange-tint: #FFEDE5;
  --brand-orange-wash: #FFF5F0;
  /* Back-compat: older markup and any partial still says "yellow". */
  --brand-yellow: var(--brand-orange);
  --brand-yellow-hover: var(--brand-orange-hover);
  --brand-black: #0A0A0A;

  /* Surface tokens, light mode */
  --surface:        #FFFFFF;
  --surface-alt:    #FAFAFA;   /* ink-50 */
  --surface-muted:  #F4F4F5;   /* ink-100 */
  --surface-strong: #0A0A0A;   /* black section bg */

  /* Text tokens, light mode */
  --text-strong:    #0A0A0A;
  --text:           #18181B;   /* ink-900 */
  --text-muted:     #52525B;   /* ink-600 */
  --text-subtle:    #71717A;   /* ink-500 */
  --text-inverse:   #FAFAFA;   /* for text on dark surfaces */

  /* Borders */
  --border:         #E4E4E7;   /* ink-200 */
  --border-strong:  #D4D4D8;   /* ink-300 */

  /* Header glass effect */
  --header-bg:      rgba(255, 255, 255, 0.85);
  --header-border:  rgba(228, 228, 231, 0.7);

  /* Form inputs */
  --input-bg:       #FFFFFF;
  --input-border:   #D4D4D8;
  --input-text:     #0A0A0A;
  --input-placeholder: #A1A1AA;

  /* Focus indicator */
  --focus-ring:     rgba(255, 90, 31, 0.55);
  --focus-outline:  #0A0A0A;
}

@media (prefers-color-scheme: dark) and (kazi-dark-enabled: yes) {
  :root {
    --surface:        #0A0A0A;
    --surface-alt:    #18181B;
    --surface-muted:  #27272A;
    --surface-strong: #18181B;

    --text-strong:    #FAFAFA;
    --text:           #F4F4F5;
    --text-muted:     #A1A1AA;
    --text-subtle:    #71717A;
    --text-inverse:   #0A0A0A;

    --border:         #27272A;
    --border-strong:  #3F3F46;

    --header-bg:      rgba(10, 10, 10, 0.85);
    --header-border:  rgba(63, 63, 70, 0.7);

    --input-bg:       #18181B;
    --input-border:   #3F3F46;
    --input-text:     #FAFAFA;
    --input-placeholder: #71717A;

    --focus-ring:     rgba(255, 90, 31, 0.7);
    --focus-outline:  #FF5A1F;
  }
}

/* Manual override, `[data-theme="light"]` or `[data-theme="dark"]` on <html>
   forces a theme. Reserved for a future toggle button. */
html[data-theme="light"] :root,
html[data-theme="light"] { color-scheme: light; }
html[data-theme="dark"]  :root,
html[data-theme="dark"]  { color-scheme: dark; }

/* ---------- GLOBAL ---------- */

html {
  -webkit-font-smoothing: antialiased;
  -moz-osx-font-smoothing: grayscale;
  text-rendering: optimizeLegibility;
  color-scheme: light dark;
}

body {
  font-family: 'Inter', ui-sans-serif, system-ui, -apple-system, sans-serif;
  font-feature-settings: 'cv11', 'ss01', 'ss03';
  color: var(--text);
  background: var(--surface);
  min-height: 100vh;
}

.display {
  font-family: 'Inter Tight', 'Inter', ui-sans-serif, system-ui, sans-serif;
  font-feature-settings: 'cv11', 'ss01';
  letter-spacing: -0.025em;
}

/* ---------- ACCESSIBILITY: SKIP-TO-CONTENT LINK ---------- */

.skip-link {
  position: absolute;
  left: 0;
  top: 0;
  padding: 12px 18px;
  background: var(--brand-black);
  color: var(--brand-orange);
  font-weight: 700;
  text-decoration: none;
  border-radius: 0 0 14px 0;
  transform: translateY(-110%);
  transition: transform 0.18s ease;
  z-index: 1000;
}
.skip-link:focus {
  transform: translateY(0);
  outline: 3px solid var(--brand-orange);
  outline-offset: 2px;
}

/* ---------- ACCESSIBILITY: FOCUS-VISIBLE ---------- */

/* Default, only show on keyboard focus, not mouse clicks. */
*:focus { outline: none; }
*:focus-visible {
  outline: 3px solid var(--focus-outline);
  outline-offset: 2px;
  border-radius: 6px;
}

/* Buttons + links inside dark surfaces, invert ring color */
.bg-brand-black *:focus-visible,
.bg-brand-yellow *:focus-visible,
footer *:focus-visible {
  outline-color: var(--brand-orange);
}

/* ---------- HEADER ---------- */

.site-header {
  background: var(--header-bg);
  backdrop-filter: saturate(180%) blur(12px);
  -webkit-backdrop-filter: saturate(180%) blur(12px);
  border-bottom: 1px solid var(--header-border);
}

/* ---------- CARD HOVER LIFT ---------- */

.tile {
  transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.tile:hover {
  box-shadow: 0 14px 32px -10px rgba(10, 10, 10.14);
  border-color: var(--border-strong);
}

/* ---------- BUTTONS ---------- */

.btn-primary {
  /* Owner, 2026-08-26: the scheme is orange and white, and there was "too much
     black on the screen". The primary call to action carries the brand colour.
     Text is near-black, not white: measured, #FF5A1F on white text is 3.12:1
     and FAILS WCAG AA for text this size, while #FF5A1F on #0A0A0A is 6.35:1
     and passes. */
  background: var(--brand-orange-ink);
  color: #FFFFFF;
  transition: background-color .15s ease, transform .15s ease;
}
.btn-primary:hover {
  background: var(--brand-orange-btn-hover);
}

.btn-accent {
  /* Same rule as .btn-primary: white words need a background dark enough to
     carry them. #C7400E gives 5.04:1, the brand #FF5A1F only 3.12:1. */
  background: var(--brand-orange-ink);
  color: #FFFFFF;
  transition: background-color .15s ease, transform .15s ease;
}
.btn-accent:hover {
  background: var(--brand-orange-btn-hover);
}

/* In dark mode, the primary button looks weak (black on near-black).
   Invert to yellow-on-black for better contrast. */
@media (prefers-color-scheme: dark) and (kazi-dark-enabled: yes) {
  .btn-primary {
    background: var(--brand-orange);
    color: var(--brand-black);
  }
  .btn-primary:hover {
    background: var(--brand-orange-hover);
  }
}

/* ---------- PROSE (legal pages) ---------- */

.prose-kazi {
  max-width: 68ch;
  line-height: 1.7;
  color: var(--text);
  font-size: 1.0625rem;
}
.prose-kazi h1 {
  font-family: 'Inter Tight', 'Inter', sans-serif;
  font-weight: 800;
  font-size: 2.75rem;
  line-height: 1.04;
  letter-spacing: -0.028em;
  color: var(--text-strong);
  margin: 0 0 0.5em;
}
.prose-kazi h2 {
  font-family: 'Inter Tight', 'Inter', sans-serif;
  font-weight: 800;
  font-size: 1.75rem;
  line-height: 1.15;
  letter-spacing: -0.022em;
  margin: 2em 0 0.5em;
  color: var(--text-strong);
}
.prose-kazi h3 {
  font-family: 'Inter Tight', 'Inter', sans-serif;
  font-weight: 700;
  font-size: 1.25rem;
  letter-spacing: -0.015em;
  margin: 1.5em 0 0.5em;
  color: var(--text-strong);
}
.prose-kazi p { margin: 0 0 1em; }
.prose-kazi ul.prose-kazi ol { margin: 0 0 1em 1.5em; }
.prose-kazi li { margin: 0.3em 0; }
.prose-kazi table {
  width: 100%;
  border-collapse: collapse;
  margin: 1.25em 0;
  font-size: 0.95rem;
}
.prose-kazi th.prose-kazi td {
  text-align: left;
  padding: 0.6em 0.85em;
  border-bottom: 1px solid var(--border);
  vertical-align: top;
}
.prose-kazi th {
  background: var(--surface-muted);
  font-weight: 700;
  color: var(--text-strong);
}
.prose-kazi a {
  color: var(--text-strong);
  text-decoration: underline;
  text-decoration-color: var(--brand-orange);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
}
.prose-kazi a:hover {
  text-decoration-thickness: 3px;
}
.prose-kazi code {
  font-family: 'JetBrains Mono', ui-monospace, monospace;
  background: var(--surface-muted);
  padding: 0.1em 0.4em;
  border-radius: 4px;
  font-size: 0.9em;
}
.prose-kazi blockquote {
  border-left: 3px solid var(--brand-orange);
  padding: 0.35em 0 0.35em 1.1em;
  margin: 1.25em 0;
  color: var(--text-muted);
  font-style: italic;
}

/* ---------- COVERAGE MAP PINS ---------- */

.kazi-pin {
  background: none !important;
  border: none !important;
  position: relative;
}
.kazi-pin .dot {
  position: absolute;
  border-radius: 9999px;
  box-shadow: 0 2px 6px rgba(0, 0, 0.25), 0 0 0 2px var(--surface);
}
.kazi-pin-active .dot {
  inset: 14px;
  background: var(--brand-orange);
}
.kazi-pin-active .pulse {
  position: absolute;
  inset: 0;
  border-radius: 9999px;
  background: rgba(255, 90, 31.35);
  animation: kazi-pulse 2.2s ease-out infinite;
}
.kazi-pin-planned .dot {
  inset: 2px;
  background: var(--brand-black);
}
@keyframes kazi-pulse {
  0%   { transform: scale(0.55); opacity: 0.7; }
  100% { transform: scale(1.35); opacity: 0; }
}

/* Respect reduced-motion preference */
@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.001ms !important;
    animation-iteration-count: 1 !important;
    transition-duration: 0.001ms !important;
    scroll-behavior: auto !important;
  }
  .kazi-pin-active .pulse {
    animation: none;
    opacity: 0;
  }
}

.leaflet-popup-content-wrapper {
  border-radius: 12px !important;
  box-shadow: 0 8px 28px -8px rgba(10, 10, 10.2) !important;
}
.leaflet-popup-content {
  margin: 12px 16px !important;
  font-family: 'Inter', sans-serif !important;
}

/* ---------- TOUCH-TARGET MINIMUMS (WCAG 2.5.5) ---------- */

button,
[role="button"],
input[type="submit"],
input[type="button"],
a.tile,
a[href].inline-flex {
  min-height: 44px;
}

/* ---------- PRINT STYLES ---------- */

@media print {
  /* Strip nav, footer, banners, forms, print only the actual content. */
  .site-header,
  footer,
  #kazi-consent,
  #kazi-success-banner,
  form,
  .leaflet-container,
  button {
    display: none !important;
  }

  body {
    background: #FFFFFF !important;
    color: #000000 !important;
    font-size: 11pt;
  }

  a {
    color: #000000 !important;
    text-decoration: underline;
  }

  /* Print URL after every link so a reader can see destinations. */
  a[href^="http"]::after,
  a[href^="mailto:"]::after {
    content: " (" attr(href) ")";
    font-size: 0.85em;
    color: #555;
  }

  /* Avoid page breaks inside paragraphs / tables / headings. */
  p, table, h1, h2, h3 { page-break-inside: avoid; }
  h1, h2, h3 { page-break-after: avoid; }

  .prose-kazi { max-width: none; font-size: 11pt; }
  .prose-kazi h1 { font-size: 22pt; }
  .prose-kazi h2 { font-size: 16pt; }
  .prose-kazi h3 { font-size: 13pt; }
}

/* ---------- DARK MODE TAILWIND OVERRIDES ---------- */

@media (prefers-color-scheme: dark) and (kazi-dark-enabled: yes) {
  body {
    background: var(--surface);
    color: var(--text);
  }

  /* `bg-white` in dark mode → bg-near-black */
  .bg-white {
    background-color: var(--surface) !important;
  }
  /* `bg-ink-50` (used for "How it works" + "Get the app" sections) */
  .bg-ink-50 {
    background-color: var(--surface-alt) !important;
  }
  /* Borders on cards */
  .border-ink-200 {
    border-color: var(--border) !important;
  }
  .border-ink-300 {
    border-color: var(--border-strong) !important;
  }

  /* Default text colors used across cards */
  .text-brand-black {
    color: var(--text-strong) !important;
  }
  .text-ink-900 {
    color: var(--text-strong) !important;
  }
  .text-ink-800,
  .text-ink-700 {
    color: var(--text) !important;
  }
  .text-ink-600 {
    color: var(--text-muted) !important;
  }
  .text-ink-500 {
    color: var(--text-subtle) !important;
  }
  .text-ink-400 {
    color: var(--text-subtle) !important;
  }

  /* Inputs */
  input, select, textarea {
    background-color: var(--input-bg) !important;
    border-color: var(--input-border) !important;
    color: var(--input-text) !important;
  }
  input::placeholder, textarea::placeholder {
    color: var(--input-placeholder) !important;
  }

  /* Yellow sections (merchant block, trust strip) stay yellow but the
     embedded text-brand-black should NOT invert, override the override. */
  .bg-brand-yellow .text-brand-black,
  .bg-brand-yellow .text-ink-900,
  .bg-brand-yellow .text-ink-800,
  .bg-brand-yellow .text-ink-700 {
    color: #0A0A0A !important;
  }

  /* White cards INSIDE yellow sections (driver apply form, merchant pricing
     blocks etc.) must stay white-on-dark, otherwise the bg-white invert
     above makes the card go near-black on top of the still-orange section
     and the labels (forced dark by the rule above) become invisible.
     The whole card subtree therefore stays in "light mode" colors. */
  .bg-brand-yellow .bg-white {
    background-color: #FFFFFF !important;
    color-scheme: light;
  }
  .bg-brand-yellow .bg-white,
  .bg-brand-yellow .bg-white .text-ink-900,
  .bg-brand-yellow .bg-white .text-ink-800,
  .bg-brand-yellow .bg-white .text-ink-700 {
    color: #0A0A0A !important;
  }
  .bg-brand-yellow .bg-white .text-ink-600 {
    color: #52525B !important;
  }
  .bg-brand-yellow .bg-white .text-ink-500 {
    color: #71717A !important;
  }
  .bg-brand-yellow .bg-white .border-ink-200 {
    border-color: #E4E4E7 !important;
  }
  /* Inputs/selects/textareas inside a white-on-yellow card need to stay
     light too, the global rule above would otherwise paint them near-black. */
  .bg-brand-yellow .bg-white input,
  .bg-brand-yellow .bg-white select,
  .bg-brand-yellow .bg-white textarea {
    background-color: #FFFFFF !important;
    color: #0A0A0A !important;
    border-color: #E4E4E7 !important;
  }
  .bg-brand-yellow .bg-white input::placeholder,
  .bg-brand-yellow .bg-white textarea::placeholder {
    color: #71717A !important;
  }

  /* Same for the consent banner (manual background), keep its own colors. */
  #kazi-consent .text-brand-black { color: #0A0A0A !important; }
}

/* ===========================================================================
   KAZI HOVER SYSTEM
   ---------------------------------------------------------------------------
   Owner, 2026-08-26: "the colours arnt working [on the nav] ... add beautiful
   hover effects over everything, different hover effect types for things".

   DIFFERENT KINDS OF THING GET DIFFERENT FEEDBACK. A nav link, a button, a
   card and a photo are not the same object, so they should not all just get
   darker. The vocabulary here:

     nav link      underline sweeps in from the left, in brand orange
     primary CTA   lifts, and the shadow blooms under it
     accent CTA    brightens toward ochre200 and lifts
     ghost / quiet fills with the orange wash, border warms
     card / tile   lifts, border warms to orange, shadow deepens
     photo card    the image scales inside a fixed frame, nothing else moves
     footer link   colour warms to orange, underline slides in
     icon chip     scales and rotates a touch, like it is being picked up
     prose link    underline thickens rather than moving

   Timings are short (140-220 ms) and use a single easing so the whole site
   feels like one object. Everything here is transform / opacity / colour, so
   nothing triggers layout. The reduced-motion block further down already
   neutralises every transition, so this respects that automatically.
   ========================================================================= */

:root {
  --kazi-ease: cubic-bezier(.2.7.3, 1);
  --kazi-fast: 140ms;
  --kazi-base: 200ms;
}

/* ---------- 1. NAV LINKS: underline sweep ---------- */

.site-header nav a {
  position: relative;
  color: var(--text-muted);
  transition: color var(--kazi-fast) var(--kazi-ease);
}
.site-header nav a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -6px;
  height: 2px;
  width: 100%;
  border-radius: 2px;
  background: var(--brand-orange);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--kazi-base) var(--kazi-ease);
}
.site-header nav a:hover,
.site-header nav a:focus-visible {
  color: var(--text-strong);
}
.site-header nav a:hover::after,
.site-header nav a:focus-visible::after {
  transform: scaleX(1);
}
/* The current page keeps its underline, at rest. */
.site-header nav a[aria-current="page"] {
  color: var(--text-strong);
}
.site-header nav a[aria-current="page"]::after {
  transform: scaleX(1);
}

/* Mobile menu rows: warm wash instead of an underline, because they are rows. */
#mobile-menu a {
  border-radius: 10px;
  padding: 6px 10px;
  margin: -6px -10px;
  transition: background-color var(--kazi-fast) var(--kazi-ease),
              color var(--kazi-fast) var(--kazi-ease);
}
#mobile-menu a:hover {
  background: var(--brand-orange-wash);
  color: var(--brand-orange-ink);
}

/* ---------- 2. BUTTONS: lift + shadow bloom ---------- */

.btn-primary,
.btn-accent {
  will-change: transform;
  transition: background-color var(--kazi-fast) var(--kazi-ease),
              transform var(--kazi-base) var(--kazi-ease),
              box-shadow var(--kazi-base) var(--kazi-ease);
}
.btn-primary:hover {
  transform: translateY(-2px);
  box-shadow: 0 10px 24px -8px rgba(10, 10, 10.45);
}
.btn-primary:active {
  transform: translateY(0);
  box-shadow: 0 2px 6px -2px rgba(10, 10, 10.35);
}
.btn-accent:hover {
  transform: translateY(-2px);
  box-shadow: 0 12px 26px -10px rgba(255, 90, 31.55);
}
.btn-accent:active {
  transform: translateY(0);
  box-shadow: 0 2px 6px -2px rgba(255, 90, 31.4);
}

/* Quiet / ghost buttons: bordered pills that are not the primary action. */
a.rounded-full.border,
button.rounded-full.border {
  transition: background-color var(--kazi-fast) var(--kazi-ease),
              border-color var(--kazi-fast) var(--kazi-ease),
              color var(--kazi-fast) var(--kazi-ease),
              transform var(--kazi-base) var(--kazi-ease);
}
a.rounded-full.border:hover,
button.rounded-full.border:hover {
  border-color: var(--brand-orange);
  color: var(--brand-orange-ink);
  background: var(--brand-orange-wash);
  transform: translateY(-1px);
}

/* ---------- 3. CARDS AND TILES: lift, and the border warms ---------- */

.tile:hover {
  transform: translateY(-3px);
  border-color: var(--brand-orange);
  box-shadow: 0 18px 38px -14px rgba(255, 90, 31.28);
}

/* Generic bordered cards that are links. Scoped to anchors so a plain
   decorative panel does not start reacting to the mouse. */
a.rounded-\[28px\],
a.rounded-\[32px\],
a.rounded-kazi,
a.rounded-3xl {
  transition: transform var(--kazi-base) var(--kazi-ease),
              box-shadow var(--kazi-base) var(--kazi-ease),
              border-color var(--kazi-fast) var(--kazi-ease);
}
a.rounded-\[28px\]:hover,
a.rounded-\[32px\]:hover,
a.rounded-kazi:hover,
a.rounded-3xl:hover {
  transform: translateY(-3px);
  border-color: var(--brand-orange);
  box-shadow: 0 18px 38px -14px rgba(255, 90, 31.28);
}

/* ---------- 4. PHOTO CARDS: the image moves, the frame does not ---------- */

/* Bound to the markup that actually exists. The image cards are
   `.tile` with `overflow-hidden`, and there is no `.media-zoom` class
   anywhere in the HTML, so a rule keyed on one would have been dead CSS,
   which is the same disease as the missing hover layer. `:has()` is
   progressive: a browser without it simply gets no zoom. */
.media-zoom {
  overflow: hidden;
}
.media-zoom img,
.tile:has(img) img {
  transition: transform 420ms var(--kazi-ease);
  will-change: transform;
}
.media-zoom:hover img,
.tile:has(img):hover img {
  transform: scale(1.045);
}
/* The frame lifts, the picture grows. Doing both to the same element would
   read as a wobble. */
.tile:has(img):hover {
  transform: translateY(-3px);
}

/* ---------- 5. FOOTER LINKS: warm + underline slide ---------- */

footer a {
  position: relative;
  transition: color var(--kazi-fast) var(--kazi-ease);
}
footer a::after {
  content: "";
  position: absolute;
  left: 0;
  bottom: -2px;
  height: 1px;
  width: 100%;
  background: var(--brand-orange);
  transform: scaleX(0);
  transform-origin: left center;
  transition: transform var(--kazi-base) var(--kazi-ease);
}
footer a:hover {
  color: var(--brand-orange);
}
footer a:hover::after {
  transform: scaleX(1);
}
/* The logo link in the footer is an image, not text. No underline on it. */
footer a:has(img)::after,
footer a[aria-label]::after {
  display: none;
}

/* ---------- 6. ICON CHIPS: picked up, not pressed ---------- */

.icon-chip,
[class*="rounded-2xl"] > i[data-lucide],
[class*="rounded-xl"] > i[data-lucide] {
  transition: transform var(--kazi-base) var(--kazi-ease),
              color var(--kazi-fast) var(--kazi-ease);
}
a:hover .icon-chip,
a:hover > [class*="rounded-2xl"] > i[data-lucide] {
  transform: scale(1.08) rotate(-4deg);
}

/* ---------- 7. PROSE LINKS: thicken, do not jump ---------- */

.prose-kazi a {
  text-decoration-color: var(--brand-orange);
  text-decoration-thickness: 2px;
  text-underline-offset: 3px;
  transition: text-decoration-thickness var(--kazi-fast) var(--kazi-ease),
              color var(--kazi-fast) var(--kazi-ease);
}
.prose-kazi a:hover {
  color: var(--brand-orange-ink);
  text-decoration-thickness: 4px;
}

/* ---------- 8. THE MASTHEAD LOGO ---------- */

.site-header > div > a[aria-label] img {
  transition: transform var(--kazi-base) var(--kazi-ease);
}
.site-header > div > a[aria-label]:hover img {
  transform: scale(1.04);
}

/* ---------- 9. DARK MODE ---------- */

@media (prefers-color-scheme: dark) and (kazi-dark-enabled: yes) {
  #mobile-menu a:hover {
    background: rgba(255, 90, 31.12);
    color: var(--brand-orange-hover);
  }
  a.rounded-full.border:hover,
  button.rounded-full.border:hover {
    background: rgba(255, 90, 31.12);
    color: var(--brand-orange-hover);
  }
  .tile:hover,
  a.rounded-\[28px\]:hover,
  a.rounded-\[32px\]:hover,
  a.rounded-kazi:hover,
  a.rounded-3xl:hover {
    box-shadow: 0 18px 38px -14px rgba(255, 90, 31.45);
  }
  .prose-kazi a:hover {
    color: var(--brand-orange-hover);
  }
}

/* ---------- 10. TOUCH DEVICES ---------- */

/* A phone has no hover. Leaving these active makes a tapped card stick in its
   hover state until you tap elsewhere, which reads as a bug. */
@media (hover: none) {
  .site-header nav a::after,
  footer a::after {
    display: none;
  }
  .btn-primary:hover,
  .btn-accent:hover,
  .tile:hover,
  a.rounded-\[28px\]:hover,
  a.rounded-\[32px\]:hover,
  a.rounded-kazi:hover,
  a.rounded-3xl:hover {
    transform: none;
  }
  .media-zoom:hover img {
    transform: none;
  }
}

/* ---------- BRAND MASCOT (hero) ---------- */

/* The clip is 640x640 with a pure-white background, sitting on a white hero,
   so there is deliberately no card, border, shadow or radius on it. Anything
   like that would draw the box we just spent effort removing. */
/* MOBILE FIRST. Below lg the clip is in NORMAL FLOW, under the copy, centred.
   It used to be `hidden lg:block` and therefore absent on every phone, which is
   how the owner found it. Un-hiding alone is not the fix: the desktop rule is
   position:absolute right:0, which at 375px drops the clip straight onto the
   headline. The hero is a flex column here so the order can be stated. */
.hero-inner {
  display: flex;
  flex-direction: column;
}
.hero-inner > .max-w-5xl { order: 1; }

.kazi-mascot {
  order: 2;
  width: min(220px, 62vw);
  margin: 28px auto 0;
  pointer-events: none;      /* it is decoration; never eat a click */
  z-index: 0;
}

@media (min-width: 1024px) {
  /* DESKTOP, unchanged: anchored to the BOTTOM RIGHT of the hero, level with
     the paragraph and the buttons. The headline is max-w-5xl and fills the row,
     but the paragraph is max-w-2xl (672px), so this is the only band with room
     to spare. */
  .hero-inner { display: block; }
  .kazi-mascot {
    position: absolute;
    right: 0;
    bottom: 24px;
    width: 250px;
    margin: 0;
  }
}
.kazi-mascot video {
  display: block;
  width: 100%;
  height: auto;
  /* Feather the bottom edge into the page so the 640px frame does not end in
     a hard horizontal line where the white of the clip meets the white of the
     hero at a slightly different tone. */
  -webkit-mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
  mask-image: linear-gradient(to bottom, #000 78%, transparent 100%);
}

/* The headline and the buttons must stay above and clickable. */
.kazi-mascot ~ .max-w-5xl {
  position: relative;
  z-index: 1;
}

@media (min-width: 1280px) {
  .kazi-mascot {
    width: 320px;
    bottom: 16px;
  }
}
@media (min-width: 1536px) {
  .kazi-mascot {
    width: 380px;
  }
}

/* Someone who asked the OS for less motion gets the poster frame, not a loop. */
@media (prefers-reduced-motion: reduce) {
  .kazi-mascot video {
    display: none;
  }
  .kazi-mascot {
    background: url('/assets/kazi-mascot-poster.jpg') center / contain no-repeat;
    aspect-ratio: 1 / 1;
  }
}

/* ---------- HERO TRUST STRIP ---------- */

/* Owner, 2026-08-26: the words and the icons on the orange banner must be
   white. Measured: white on the brand #FF5A1F is 3.12:1 and FAILS WCAG AA at
   this text size. On #C7400E it is 5.04:1 and passes, at the same hue, so the
   strip carries the deeper orange and the copy is genuinely white. */
.trust-strip {
  background: var(--brand-orange-ink);
  color: #FFFFFF;
}
.trust-strip svg,
.trust-strip i[data-lucide] {
  color: #FFFFFF;
  stroke: currentColor;
}

/* ---------- SITE ANNOUNCEMENT BAR ---------- */

/* Same type family as the rest of the site, deliberately one size down from
   body copy so it reads as a notice rather than as content. White on the
   deeper brand orange, which measures 5.04:1 and clears WCAG AA; white on the
   brighter #FF5A1F would be 3.12:1 and fail. */
.announce {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 10px;
  padding: 10px 20px;
  background: var(--brand-orange-ink);
  color: #FFFFFF;
  font-size: 13.5px;
  font-weight: 600;
  letter-spacing: -0.01em;
  line-height: 1.4;
  text-align: center;
  text-decoration: none;
  transition: background-color var(--kazi-fast, 140ms) ease;
}
.announce:hover {
  background: var(--brand-orange-btn-hover);
}
.announce-arrow {
  width: 15px;
  height: 15px;
  flex: 0 0 auto;
  transition: transform var(--kazi-base, 200ms) var(--kazi-ease, ease);
}
.announce:hover .announce-arrow {
  transform: translateX(3px);
}
.announce:focus-visible {
  outline: 3px solid #FFFFFF;
  outline-offset: -3px;
}
@media (max-width: 640px) {
  .announce {
    font-size: 12.5px;
    padding: 9px 14px;
  }
  .announce-arrow {
    display: none;
  }
}

/* ===========================================================================
   HEADLINE SHINE + ROTATING WORDS
   ---------------------------------------------------------------------------
   Owner, 2026-08-26: "ALL HEADLINE WORDS SHOULD HAVE A SLOW CONTINUOUS SILVER
   SHINE THAT GOES OVER THEM, SOME MAIN HEADING WORDS SHOULD APPEAR SLOWLY AND
   DISAPPEAR SLOWLY AND CHANGE, ALL THIS APPLIES THROUGHOUT THE WEBSITE".

   Applied from CSS to h1 and h2 directly, so it covers all 138 headings on the
   site without touching a single line of markup, and it keeps covering new
   ones. Measured: exactly one heading contains a coloured inline span
   (for-drivers "Set your own hours."); background-clip: text would flatten
   that colour, so it opts out with .no-shine.

   🔴 THE FAILURE MODE THIS GUARDS AGAINST. `color: transparent` with
   `background-clip: text` means that if the background does not paint, the
   heading is INVISIBLE, not merely unstyled. So the clip is only ever applied
   inside an @supports test, and reduced-motion puts the solid colour back
   rather than freezing a half-swept gradient.
   ========================================================================= */

:root {
  --shine-base: #0A0A0A;   /* the heading's real colour on light surfaces */
  --shine-hi:   #A8AEB6;   /* silver, light enough to read as a sheen */
  --shine-secs: 7s;
}

/* White headings on the dark bands need the sheen to go the other way: the
   base dips slightly off-white and the highlight is pure white. */
.bg-brand-black,
.bg-ink-900,
footer {
  --shine-base: #F2F2F3;
  --shine-hi:   #FFFFFF;
}

/* On the orange bands the type is near-black again. */
.bg-brand-yellow,
.bg-brand-orange,
.trust-strip {
  --shine-base: #0A0A0A;
  --shine-hi:   #6B4636;
}

@supports ((-webkit-background-clip: text) or (background-clip: text)) {
  h1:not(.no-shine),
  h2:not(.no-shine) {
    background-image: linear-gradient(
      100deg,
      var(--shine-base) 0%,
      var(--shine-base) 42%,
      var(--shine-hi) 50%,
      var(--shine-base) 58%,
      var(--shine-base) 100%
    );
    background-size: 260% 100%;
    /* 🔴 MUST REPEAT. With no-repeat, any part of the heading the gradient is
       not currently covering has NO background, and because the text is
       painted from that background it becomes INVISIBLE, not just unstyled.
       Observed live: "Send a parcel." rendered as "d a parcel." with the
       first glyphs missing while the sweep was off to one side. Repeating
       guarantees every glyph is always covered by some part of the ramp. */
    background-repeat: repeat;
    -webkit-background-clip: text;
    background-clip: text;
    -webkit-text-fill-color: transparent;
    color: transparent;
    animation: kazi-shine var(--shine-secs) linear infinite;
    /* The sweep is decorative. Without this a long heading repaints its whole
       box every frame. */
    will-change: background-position;
  }
}

@keyframes kazi-shine {
  0%   { background-position: 0 0; }
  100% { background-position: -260% 0; }
}

/* ---------- ROTATING WORDS ---------- */

/* inline-grid stacks every option in ONE cell, so the container is as wide as
   the widest word and nothing reflows when the word changes. A plain
   swap would make the whole headline jump on every cycle. */
.word-rotate {
  display: inline-grid;
  vertical-align: baseline;
}
.word-rotate > .word {
  grid-area: 1 / 1;
  opacity: 0;
  transform: translateY(0.12em);
  transition: opacity 1.1s ease, transform 1.1s ease;
  /* keep the widest word reserving the space even while invisible */
  visibility: hidden;
}
.word-rotate > .word.is-on {
  opacity: 1;
  transform: none;
  visibility: visible;
}
/* Before the script runs, show the first word so the sentence is never a gap. */
.word-rotate > .word:first-child {
  visibility: visible;
  opacity: 1;
}
.word-rotate.is-ready > .word:first-child:not(.is-on) {
  visibility: hidden;
  opacity: 0;
}

/* ---------- REDUCED MOTION ---------- */

@media (prefers-reduced-motion: reduce) {
  h1:not(.no-shine),
  h2:not(.no-shine) {
    background-image: none;
    -webkit-text-fill-color: var(--shine-base);
    color: var(--shine-base);
    animation: none;
  }
  .word-rotate > .word {
    transition: none;
  }
  /* No cycling: the first word simply stays. */
  .word-rotate > .word:not(:first-child) {
    display: none;
  }
  .word-rotate > .word:first-child {
    visibility: visible;
    opacity: 1;
  }
}

/* ---------- FLOATING WHATSAPP ---------- */

/* #25D366 is WhatsApp's own brand green. Sits BELOW the cookie banner
   (z-index 9999) so a first-time visitor answers the consent question before
   anything else competes for the tap. */
.wa-float {
  position: fixed;
  right: 20px;
  bottom: 20px;
  z-index: 9990;
  display: inline-flex;
  align-items: center;
  gap: 0;
  height: 58px;
  padding: 0 17px;
  border-radius: 999px;
  background: #25D366;
  color: #FFFFFF;
  text-decoration: none;
  box-shadow: 0 6px 18px -4px rgba(37, 211, 102, .45),
              0 12px 30px -10px rgba(10, 10, 10, .35);
  transition: transform var(--kazi-base, 200ms) var(--kazi-ease, ease),
              box-shadow var(--kazi-base, 200ms) var(--kazi-ease, ease),
              background-color var(--kazi-fast, 140ms) ease,
              opacity 280ms var(--kazi-ease, ease);
}
.wa-float:hover {
  background: #1FBE5A;
  transform: translateY(-3px);
  box-shadow: 0 10px 24px -4px rgba(37, 211, 102, .55),
              0 18px 40px -12px rgba(10, 10, 10, .4);
}
.wa-float:active {
  transform: translateY(0);
}
.wa-glyph {
  width: 26px;
  height: 26px;
  flex: 0 0 auto;
}

/* The label is collapsed to zero width at rest and opens on hover, so the
   button is a neat circle until someone reaches for it. */
.wa-label {
  max-width: 0;
  overflow: hidden;
  white-space: nowrap;
  font-size: 14.5px;
  font-weight: 700;
  letter-spacing: -0.01em;
  opacity: 0;
  transition: max-width 260ms var(--kazi-ease, ease),
              opacity 200ms ease,
              margin-left 260ms var(--kazi-ease, ease);
}
.wa-float:hover .wa-label,
.wa-float:focus-visible .wa-label {
  max-width: 160px;
  margin-left: 10px;
  opacity: 1;
}

/* A slow outward ring, so it reads as live without being a nagging blinker. */
.wa-ring {
  position: absolute;
  inset: 0;
  border-radius: inherit;
  box-shadow: 0 0 0 0 rgba(37, 211, 102, .55);
  animation: wa-pulse 2.8s ease-out infinite;
  pointer-events: none;
}
@keyframes wa-pulse {
  0%   { box-shadow: 0 0 0 0 rgba(37, 211, 102, .5); }
  70%  { box-shadow: 0 0 0 16px rgba(37, 211, 102, 0); }
  100% { box-shadow: 0 0 0 0 rgba(37, 211, 102, 0); }
}

.wa-float:focus-visible {
  outline: 3px solid var(--brand-orange);
  outline-offset: 3px;
}

/* ---- OFF SCREEN while the consent question is open ----
   The consent card and this button both sit bottom-right, and on a phone the
   card is 343px wide. Measured on the live site 2026-08-27 at 375x812:
   2704 of the circle's 2916 px2 sat BEHIND the card, so a first-time visitor
   on mobile saw a green crescent poking out of the card's corner.
   z-index alone never fixed this. The card correctly wins the tap (verified,
   it is not stealing it), it just does not hide what sits under it.

   Two mechanisms, because neither covers every phone on its own:
     body:has(#kazi-consent)  declarative, and it also covers
                              KaziConsent.show() and .reset() for free
     html.kazi-consent-open   set by kazi-consent.js, for engines with no :has()
   Both fail OPEN. A browser with JS off, or with no :has(), still shows the
   button rather than losing it, so the worst case is today's behaviour and
   never a silently missing button. */
html.kazi-consent-open .wa-float,
body:has(#kazi-consent) .wa-float {
  opacity: 0;
  visibility: hidden;
  pointer-events: none;
}

@media (max-width: 640px) {
  .wa-float {
    right: 14px;
    bottom: 14px;
    height: 54px;
    padding: 0 14px;
  }
  /* No hover on a phone, so the label can never open: keep it out of the
     layout entirely rather than leaving a dead element in the button. */
  .wa-label {
    display: none;
  }
}

@media (prefers-reduced-motion: reduce) {
  .wa-ring {
    animation: none;
    box-shadow: none;
  }
  /* The reveal after the consent choice snaps rather than fades. */
  .wa-float {
    transition: none;
  }
}
