/* ==========================================================================
   Tampa Extreme Building Maintenance — "Red Noir"
   Shared stylesheet. Every page loads this; no page defines its own <style>.

   Extracted from design/red-noir-reference.html. Tailwind (CDN) supplies the
   utility layer; this file holds tokens, components, motion, and a11y.

   NOTE: Tailwind is loaded from the CDN for zero-build simplicity. To move to
   a compiled build later, see the "Compiled Tailwind" note in README.md — the
   class names here are all standard utilities, so a `tailwindcss -i -o` pass
   over the HTML plus this file is the whole migration.
   ========================================================================== */

:root {
  --accent-red: #ef233c;
  --accent-red-glow: rgba(239, 35, 60, 0.5);
  --oxblood: #1a0505;
  --surface: rgba(255, 255, 255, 0.05);
  --hairline: rgba(255, 255, 255, 0.1);
  /* Before/after slider position. Default 50% so the component renders
     correctly with JS disabled. */
  --pos: 50%;
}

@property --gradient-angle {
  syntax: "<angle>";
  initial-value: 0deg;
  inherits: false;
}

/* --------------------------------------------------------------------------
   Type
   -------------------------------------------------------------------------- */

.font-manrope { font-family: 'Manrope', system-ui, sans-serif; }
.font-inter    { font-family: 'Inter', system-ui, sans-serif; }

/* Gradient-clipped headline. Used on h1/h2 display text. */
.headline-clip {
  color: transparent;
  background-clip: text;
  -webkit-background-clip: text;
  background-image: linear-gradient(
    to bottom,
    #fff 0%,
    #fff 55%,
    rgba(255, 255, 255, 0.4) 100%
  );
}

.text-stroke {
  -webkit-text-stroke: 1px rgba(255, 255, 255, 0.1);
  color: transparent;
}

::selection {
  background: var(--accent-red);
  color: #fff;
}

/* Phone numbers must never wrap mid-number.

   html-validate's `tel-non-breaking` rule wants &nbsp; and &#8209; inside the
   number instead. We deliberately do not do that: U+2011 is a different
   codepoint from an ASCII hyphen, so it would make the on-page phone number
   differ byte-for-byte from every NAP citation elsewhere on the web — which is
   the one thing local SEO most needs to stay identical. CSS gets the same
   visual result without touching the text. The rule is off in
   .htmlvalidate.json for this reason. */
a[href^="tel:"] { white-space: nowrap; }

/* --------------------------------------------------------------------------
   Keyframes
   -------------------------------------------------------------------------- */

@keyframes fade-in-up {
  from { opacity: 0; transform: translateY(20px); }
  to   { opacity: 1; transform: translateY(0); }
}

@keyframes border-spin {
  from { --gradient-angle: 0deg; }
  to   { --gradient-angle: 360deg; }
}

@keyframes animStar {
  from { transform: translateY(0); }
  to   { transform: translateY(-2000px); }
}

/* --------------------------------------------------------------------------
   Motion
   Content is visible and usable with NO animation by default. Decorative
   motion is layered on only when the visitor has not asked for reduced
   motion — so a reduced-motion user, or a CSS failure, never hides content.
   -------------------------------------------------------------------------- */

.animate-fade-up { opacity: 1; }

@media (prefers-reduced-motion: no-preference) {
  .animate-fade-up {
    opacity: 0;
    animation: fade-in-up 0.8s ease-out forwards;
  }

  .shiny-cta { animation: border-spin 2.5s linear infinite; }

  .stars-1 { animation: animStar 50s linear infinite; }
  .stars-2 { animation: animStar 80s linear infinite; }

  .status-ping { animation: ping 1.6s cubic-bezier(0, 0, 0.2, 1) infinite; }

  .ghost-btn__sweep { animation: spin 3s linear infinite; }
}

@keyframes ping {
  75%, 100% { transform: scale(2); opacity: 0; }
}

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

/* Stagger helpers — avoids inline style attributes on every element. */
.delay-1 { animation-delay: 0.1s; }
.delay-2 { animation-delay: 0.2s; }
.delay-3 { animation-delay: 0.3s; }
.delay-4 { animation-delay: 0.4s; }

/* --------------------------------------------------------------------------
   Fixed background stack
   All layers are pointer-events:none and sit behind content.
   -------------------------------------------------------------------------- */

.bg-stack {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
}

.bg-oxblood {
  position: absolute;
  inset: 0;
  background: linear-gradient(to bottom, var(--oxblood), #000);
}

.bg-glow {
  position: absolute;
  top: 50%;
  left: 50%;
  width: 800px;
  height: 800px;
  max-width: 150vw;
  max-height: 150vw;
  transform: translate(-50%, -50%);
  border-radius: 9999px;
  background: rgba(220, 38, 38, 0.05);
  filter: blur(120px);
}

.bg-grid {
  position: absolute;
  inset: 0;
  background-image:
    linear-gradient(rgba(255, 255, 255, 0.03) 1px, transparent 1px),
    linear-gradient(90deg, rgba(255, 255, 255, 0.03) 1px, transparent 1px);
  background-size: 40px 40px;
  -webkit-mask-image: radial-gradient(circle at center, #000 40%, transparent 80%);
          mask-image: radial-gradient(circle at center, #000 40%, transparent 80%);
}

/* Cursor-reactive grid. Draws the same 40px lattice as .bg-grid, but warping
   toward the pointer and rippling on click. Transparent — it composites over
   the oxblood wash, stars and red glow rather than replacing them.

   .bg-grid stays in the markup as the fallback and is only hidden once
   site.js has confirmed the canvas is actually running, so a JS failure,
   a reduced-motion preference or a touch device all still get the grid. */
.bg-grid-canvas {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transition: opacity 0.6s ease;
  -webkit-mask-image: radial-gradient(circle at center, #000 40%, transparent 80%);
          mask-image: radial-gradient(circle at center, #000 40%, transparent 80%);
}

.bg-stack[data-kinetic="on"] .bg-grid { display: none; }
.bg-stack[data-kinetic="on"] .bg-grid-canvas { opacity: 1; }

.stars-1,
.stars-2 {
  position: absolute;
  top: 0;
  left: 0;
  background: transparent;
}

.stars-1 {
  width: 1px;
  height: 1px;
  box-shadow:
    234px 124px #fff, 654px 345px #fff, 876px 12px #fff, 1200px 800px #fff,
    400px 1500px #fff, 1800px 200px #fff, 100px 1000px #fff, 900px 1900px #fff,
    500px 600px #fff, 1400px 100px #fff, 300px 400px #fff, 1600px 1200px #fff;
}

.stars-2 {
  width: 2px;
  height: 2px;
  box-shadow:
    123px 456px #fff, 789px 234px #fff, 456px 890px #fff, 1100px 300px #fff,
    200px 1200px #fff, 1500px 500px #fff, 600px 1700px #fff, 1300px 900px #fff;
}

/* --------------------------------------------------------------------------
   Light-panel sections
   A couple of home-page sections invert to a white panel for contrast against
   the otherwise all-dark page. The dot field is the .stars-1/.stars-2 device
   recolored for a light background — same box-shadow-dot trick, scoped to the
   section instead of the fixed viewport.

   These sections are only a few hundred px tall, so reusing animStar's
   2000px travel verbatim would drift the whole field out and leave the
   panel empty for most of the cycle. Instead each layer's dot list is drawn
   twice, the second copy offset by exactly one tile height (300px), and the
   animation travels that same 300px — so the moment the first copy exits
   the top, the second copy is sitting exactly where the first one started.
   The section's own overflow:hidden clips everything outside its bounds.
   -------------------------------------------------------------------------- */

.dots-black-1,
.dots-black-2 {
  position: absolute;
  top: 0;
  left: 0;
  background: transparent;
  pointer-events: none;
}

.dots-black-1 {
  width: 2px;
  height: 2px;
  box-shadow:
    60px 40px #000, 220px 110px #000, 340px 30px #000, 480px 150px #000,
    610px 60px #000, 40px 220px #000, 260px 260px #000, 420px 240px #000,
    560px 200px #000, 700px 120px #000, 780px 40px #000, 900px 180px #000,
    980px 90px #000, 1080px 220px #000, 1180px 60px #000, 1260px 160px #000,
    60px 340px #000, 220px 410px #000, 340px 330px #000, 480px 450px #000,
    610px 360px #000, 40px 520px #000, 260px 560px #000, 420px 540px #000,
    560px 500px #000, 700px 420px #000, 780px 340px #000, 900px 480px #000,
    980px 390px #000, 1080px 520px #000, 1180px 360px #000, 1260px 460px #000;
}

.dots-black-2 {
  width: 3px;
  height: 3px;
  box-shadow:
    140px 80px #000, 380px 190px #000, 520px 50px #000, 660px 230px #000,
    820px 100px #000, 1000px 40px #000, 1140px 200px #000, 160px 250px #000,
    140px 380px #000, 380px 490px #000, 520px 350px #000, 660px 530px #000,
    820px 400px #000, 1000px 340px #000, 1140px 500px #000, 160px 550px #000;
}

@media (prefers-reduced-motion: no-preference) {
  .dots-black-1 { animation: dots-float-up 16s linear infinite; }
  .dots-black-2 { animation: dots-float-up 26s linear infinite; }
}

@keyframes dots-float-up {
  from { transform: translateY(0); }
  to   { transform: translateY(-300px); }
}

/* Dark-to-white scroll transition, dropped in wherever a page hands off from
   the dark theme to a .light-panel. Starts fully transparent — so it
   continues whatever the fixed dark background is doing behind it with no
   visible seam — and fades to solid white. The last color-stop's white holds
   for any remaining height if the element is taller than the fade distance,
   though in practice this element is sized to exactly that distance and the
   .light-panel wrapper immediately below supplies the solid white beyond it. */
.panel-fade {
  height: 160px;
  background: linear-gradient(to bottom, transparent, #fff);
}

/* General-purpose light-panel override. Where .faq-light targets one
   specific shared component (.faq-item), this covers the common
   heading/body/link/link-eyebrow/image patterns repeated across many
   hand-written sections — cheaper than rewriting every text-white,
   text-zinc-300 etc. utility class at each call site. Relies on being a
   more specific selector (element/attribute + class) than the single-class
   Tailwind utility it overrides, so source order doesn't matter. */
.light-panel h2 { color: #000; }
.light-panel .text-zinc-300 { color: #3f3f46; }
.light-panel .text-zinc-400 { color: #52525b; }
.light-panel p[class*="ef233c"],
.light-panel a[class*="ef233c"] { color: #c11c2e; }
.light-panel .svc-img { background: #f4f4f5; border-color: rgba(0, 0, 0, 0.08); }
/* The before/after slider also appears inside .light-panel service sections,
   where it needs the same inverted chrome .svc-img gets. */
.light-panel .ba { background: #f4f4f5; border-color: rgba(0, 0, 0, 0.08); }

.gradient-blur {
  position: fixed;
  z-index: 40;
  inset: 0 0 auto 0;
  height: 120px;
  pointer-events: none;
  background: linear-gradient(to bottom, rgba(0, 0, 0, 0.8), transparent);
  backdrop-filter: blur(8px);
  -webkit-mask-image: linear-gradient(to bottom, #000, transparent);
          mask-image: linear-gradient(to bottom, #000, transparent);
}

/* --------------------------------------------------------------------------
   Buttons
   -------------------------------------------------------------------------- */

.shiny-cta {
  --gradient-angle: 0deg;
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  overflow: hidden;
  border-radius: 9999px;
  padding: 1rem 2.5rem;
  min-height: 3rem;
  background:
    linear-gradient(#000, #000) padding-box,
    conic-gradient(
      from var(--gradient-angle),
      transparent 0%,
      var(--accent-red) 5%,
      var(--accent-red) 15%,
      var(--accent-red) 30%,
      transparent 40%,
      transparent 100%
    ) border-box;
  border: 2px solid transparent;
  cursor: pointer;
  isolation: isolate;
}

/* Static border when motion is unwelcome — the CTA still reads as accented. */
@media (prefers-reduced-motion: reduce) {
  .shiny-cta { border-color: var(--accent-red); }
}

.shiny-cta::before {
  content: '';
  position: absolute;
  inset: 0;
  background: radial-gradient(circle at 50% 50%, #fff 0.5px, transparent 0);
  background-size: 4px 4px;
  opacity: 0.1;
  z-index: 0;
}

.shiny-cta > * { position: relative; z-index: 10; }

/* Ghost button with conic hover sweep */
.ghost-btn {
  position: relative;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: 0.5rem;
  overflow: hidden;
  border-radius: 9999px;
  padding: 1rem 2rem;
  min-height: 3rem;
  background: var(--surface);
  cursor: pointer;
  isolation: isolate;
}

.ghost-btn__ring {
  position: absolute;
  inset: 0;
  border: 1px solid var(--hairline);
  border-radius: 9999px;
}

.ghost-btn__sweep {
  position: absolute;
  inset: -100%;
  opacity: 0;
  transition: opacity 0.3s;
  background: conic-gradient(
    from 90deg at 50% 50%,
    transparent 0%,
    transparent 75%,
    var(--accent-red) 100%
  );
}

.ghost-btn:hover .ghost-btn__sweep,
.ghost-btn:focus-visible .ghost-btn__sweep { opacity: 1; }

.ghost-btn__face {
  position: absolute;
  inset: 1px;
  border-radius: 9999px;
  background: #000;
}

.ghost-btn__label {
  position: relative;
  z-index: 10;
  display: inline-flex;
  align-items: center;
  gap: 0.5rem;
}

/* --------------------------------------------------------------------------
   Cards
   -------------------------------------------------------------------------- */

.bento-card {
  position: relative;
  overflow: hidden;
  border: 1px solid var(--hairline);
  border-radius: 0.75rem;
  background: #000;
  transition: border-color 0.3s;
}

.bento-card:hover { border-color: rgba(255, 255, 255, 0.2); }

/* Radial hover glow, keyed off the card's own accent. */
.bento-card__glow {
  position: absolute;
  inset: 0;
  opacity: 0;
  transition: opacity 0.3s;
  pointer-events: none;
  background: radial-gradient(circle at top right, var(--accent-red), transparent 70%);
}

.bento-card:hover .bento-card__glow,
.bento-card:focus-within .bento-card__glow { opacity: 0.1; }

/* --------------------------------------------------------------------------
   Bento card photography
   The image is a background layer, not a band, so card heights stay exactly
   as the 700px grid lays them out and nothing reflows when photos load.
   A scrim sits between the photo and the text: it guarantees contrast
   regardless of what the photograph happens to be doing behind it, which a
   bare opacity drop does not.
   -------------------------------------------------------------------------- */

.bento-bg {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  z-index: 0;
}

/* The photo is shown undimmed. There is no card-wide tint of any kind — the
   only darkening on these cards is the gradient band behind the text below,
   which is sized to the text and nothing more. */

/* The card owns the minimum height so the photograph has room to be seen, and
   bottom-anchors its content. Previously .bento-card__body carried
   `height: 100%` for this, which made the body box fill the whole card — and
   since the protective gradient is drawn on the body, the gradient covered the
   entire photo at >=0.90 black rather than sitting behind the text. Keeping the
   stretch on the card and letting the body hug its content is what turns that
   gradient back into a band. */
.bento-card--photo {
  display: flex;
  flex-direction: column;
  justify-content: flex-end;
  min-height: 17rem;
}

.bento-card__body {
  position: relative;
  z-index: 10;
  display: flex;
  flex-direction: column;
}

/* The icon chip is a flex item now, so without this it stretches to the full
   card width and reads as an empty bar rather than a badge. */
.bento-card__body > span { align-self: flex-start; }

/* The protective gradient is anchored to the CONTENT, not to the card.

   Tuning a card-level scrim against measured photo brightness turned into
   whack-a-mole: the heading lands at a different fraction of the card depending
   on card size and breakpoint, so every gradient tuned for one case failed
   another. Anchoring the band to the body removes the variable — wherever the
   text sits, the band is behind it.

   Because the body now hugs its content, this box is only as tall as the icon
   and heading, so the band is genuinely a band: it is opaque only across the
   text itself and fades out within the bleed above it, leaving the rest of the
   photograph untouched. The negative inset carries it out through the card's
   2rem padding so it still reaches the edges. */
.bento-card__body::before {
  content: '';
  position: absolute;
  inset: -3rem -2rem -2rem -2rem;
  z-index: -1;
  pointer-events: none;
  background: linear-gradient(
    to top,
    rgba(0, 0, 0, 0.92) 0%,
    rgba(0, 0, 0, 0.88) 45%,
    rgba(0, 0, 0, 0.60) 70%,
    rgba(0, 0, 0, 0) 100%
  );
}

/* --------------------------------------------------------------------------
   Before / after slider
   A real <input type="range"> drives --pos. Both images are in the DOM and
   visible without JS (the default 50% split renders fine); JS only makes the
   split draggable. Keyboard-operable because it is a genuine range input.
   -------------------------------------------------------------------------- */

.ba {
  position: relative;
  overflow: hidden;
  border-radius: 0.75rem;
  border: 1px solid var(--hairline);
  /* Locks the box before images load, so real photos cause zero layout shift. */
  aspect-ratio: 4 / 3;
  background: #0a0a0a;
}

.ba img {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
}

.ba__before {
  clip-path: inset(0 calc(100% - var(--pos)) 0 0);
}

.ba__divider {
  position: absolute;
  top: 0;
  bottom: 0;
  left: var(--pos);
  width: 2px;
  background: var(--accent-red);
  transform: translateX(-1px);
  pointer-events: none;
  box-shadow: 0 0 12px var(--accent-red-glow);
}

/* Grip knob. Pure affordance — it says "this drags" and nothing else, so it
   never takes pointer events; the full-area range input underneath does. */
.ba__grip {
  position: absolute;
  top: 50%;
  left: var(--pos);
  z-index: 4;
  display: flex;
  align-items: center;
  justify-content: center;
  width: 2.5rem;
  height: 2.5rem;
  margin: -1.25rem 0 0 -1.25rem;
  border-radius: 9999px;
  border: 2px solid var(--accent-red);
  background: rgba(0, 0, 0, 0.72);
  box-shadow: 0 0 16px var(--accent-red-glow);
  color: #fff;
  pointer-events: none;
}

.ba__range {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  margin: 0;
  opacity: 0;
  cursor: ew-resize;
  /* 44px minimum touch target is satisfied by the full-area input. */
}

/* The input is opacity:0, so its own focus ring is invisible. Draw the ring on
   the container instead — a keyboard visitor must still see what they grabbed. */
.ba:focus-within {
  outline: 2px solid var(--accent-red);
  outline-offset: 3px;
}

.ba__label {
  position: absolute;
  bottom: 0.75rem;
  z-index: 5;
  padding: 0.25rem 0.625rem;
  border-radius: 9999px;
  background: rgba(0, 0, 0, 0.7);
  border: 1px solid var(--hairline);
  font-size: 0.625rem;
  font-weight: 700;
  letter-spacing: 0.1em;
  text-transform: uppercase;
  color: #d4d4d8;
  pointer-events: none;
}

.ba__label--before { left: 0.75rem; }
.ba__label--after  { right: 0.75rem; }

/* --------------------------------------------------------------------------
   Google review bar
   The Google "G" keeps its own brand colours — a mark is only trustworthy if
   it is the real one, and at 20px it reads as the minor icon accent the design
   system already allows. Everything else stays on the red/zinc palette.
   -------------------------------------------------------------------------- */

.gbar__mark {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 3rem;
  height: 3rem;
  flex-shrink: 0;
  border-radius: 9999px;
  background: #fff;
  box-shadow: 0 0 0 1px var(--hairline);
}

.gbar__mark svg { width: 1.5rem; height: 1.5rem; }

/* The hairline ring is white/10, which vanishes on a white panel. */
.gbar__mark--light { box-shadow: 0 0 0 1px rgba(0, 0, 0, 0.1); }

/* Five filled review stars as one repeating background tile, rather than
   twenty inline SVGs across four blocks. Decorative and aria-hidden — the
   rating itself sits next to them as real text, so nothing depends on this
   rendering. Google's own gold is used deliberately: these represent Google
   ratings, and recolouring them to the brand red would misrepresent the
   source. Same reasoning as keeping the Google "G" in its own colours. */
.stars-5 {
  display: inline-block;
  width: 6.25rem;
  height: 1.25rem;
  vertical-align: -0.25rem;
  background-repeat: repeat-x;
  background-size: 1.25rem 1.25rem;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 24 24' fill='%23fbbc04'%3E%3Cpath d='M12 17.27 18.18 21l-1.64-7.03L22 9.24l-7.19-.61L12 2 9.19 8.63 2 9.24l5.46 4.73L5.82 21z'/%3E%3C/svg%3E");
}

/* --------------------------------------------------------------------------
   Service section imagery
   width/height are on the <img> and the aspect-ratio is pinned here, so the
   box is reserved before the file arrives and nothing reflows on load.
   -------------------------------------------------------------------------- */

.svc-img {
  display: block;
  width: 100%;
  height: auto;
  aspect-ratio: 16 / 9;
  object-fit: cover;
  margin-bottom: 1.5rem;
  border-radius: 0.75rem;
  border: 1px solid var(--hairline);
  background: #0a0a0a;
}

/* --------------------------------------------------------------------------
   FAQ
   -------------------------------------------------------------------------- */

.faq-item {
  border-bottom: 1px solid var(--hairline);
}

.faq-item > summary {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.25rem 0;
  min-height: 44px;
  cursor: pointer;
  list-style: none;
  font-weight: 600;
  color: #fff;
}

.faq-item > summary::-webkit-details-marker { display: none; }

.faq-item > summary::after {
  content: '+';
  flex-shrink: 0;
  font-size: 1.5rem;
  line-height: 1;
  color: var(--accent-red);
  transition: transform 0.2s;
}

.faq-item[open] > summary::after { transform: rotate(45deg); }

.faq-item > div {
  padding-bottom: 1.25rem;
  color: #d4d4d8;
  line-height: 1.7;
}

/* .faq-item is shared with /services and /dump-trailer-rental, so the light-
   panel treatment is scoped to .faq-light rather than changing the base
   rule — those other pages keep the dark FAQ list. */
.faq-light .faq-item { border-bottom-color: rgba(0, 0, 0, 0.1); }
.faq-light .faq-item > summary { color: #000; }
.faq-light .faq-item > div { color: #3f3f46; }

/* The brand red at 4.22:1 clears AA only for large text (the "Answered"
   heading, 3:1 threshold). The phone-number link in the "How soon" answer is
   body-sized, which needs 4.5:1 — darkened here, scoped to this section only. */
.faq-light .faq-item a { color: #c11c2e; }

/* --------------------------------------------------------------------------
   Forms
   -------------------------------------------------------------------------- */

.field {
  width: 100%;
  min-height: 44px;
  padding: 0.75rem 1rem;
  border-radius: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--hairline);
  color: #fff;
  transition: border-color 0.2s;
}

.field::placeholder { color: #a1a1aa; } /* zinc-400: 8.19:1 on the field background, AA */

.field:focus {
  outline: none;
  border-color: var(--accent-red);
}

.field[aria-invalid="true"] { border-color: var(--accent-red); }

.field-error {
  display: block;
  margin-top: 0.375rem;
  font-size: 0.8125rem;
  color: #fca5a5;
}

/* Honeypot — visually gone, still reachable by bots that parse the DOM.
   Not display:none, because some bots skip those. */
.hp-field {
  position: absolute;
  left: -9999px;
  width: 1px;
  height: 1px;
  overflow: hidden;
}

/* --------------------------------------------------------------------------
   Accessibility
   -------------------------------------------------------------------------- */

.skip-link {
  position: absolute;
  left: 0.5rem;
  top: -100px;
  z-index: 100;
  padding: 0.75rem 1.25rem;
  border-radius: 0.5rem;
  background: var(--accent-red);
  color: #fff;
  font-weight: 600;
  transition: top 0.15s;
}

.skip-link:focus { top: 0.5rem; }

/* Visible focus everywhere, including on dark glass. */
:focus-visible {
  outline: 2px solid var(--accent-red);
  outline-offset: 2px;
  border-radius: 4px;
}

/* Every interactive control clears 44px. */
a[class*="nav-link"],
button,
[role="button"],
summary,
input,
select,
textarea {
  min-height: 44px;
}

/* Inline links inside prose are exempt — 44px would break line rhythm. */
.prose a { min-height: 0; }

html { scroll-behavior: smooth; }

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
}

/* Fixed pill nav is ~76px tall; anchor targets clear it. */
[id] { scroll-margin-top: 6.5rem; }

/* --------------------------------------------------------------------------
   Mobile nav
   -------------------------------------------------------------------------- */

.mobile-panel[hidden] { display: none; }
