/*
 * Utility Classes
 *
 * Single-purpose helper classes for common patterns.
 * Includes text colors and scroll-triggered animations.
 */

/* ============================================
   TEXT COLOR UTILITIES
   ============================================ */
.text-light-on-dark {
  --text-color: var(--text-light-on-dark);

  color: var(--text-light-on-dark);
}

.text-dark-on-light {
  --text-color: var(--text-dark-on-light);

  color: var(--text-dark-on-light);
}

/* ============================================
   SCROLL-TRIGGERED ANIMATIONS
   Elements start hidden/offset and animate
   to visible when .in-view is added via JS
   ============================================ */

/* Fade in */
.fade-in {
  transition: opacity 0.6s var(--cubic-bezier-1);
  opacity: 0;
}

/* Fade up (from below) */
.fade-up {
  transition: var(--fade-transition);
  transform: translateY(30px);
  opacity: 0;
}

/* Fade left (from right) */
.fade-left {
  transition: var(--fade-transition);
  transform: translateX(30px);
  opacity: 0;
}

/* Fade right (from left) */
.fade-right {
  transition: var(--fade-transition);
  transform: translateX(-30px);
  opacity: 0;
}

/* In-view state - applied via IntersectionObserver */
.in-view,
.in-view .fade-in,
.in-view .fade-up,
.in-view .fade-left,
.in-view .fade-right {
  transform: none;
  opacity: 1;
}

/* ============================================
   ACCESSIBILITY
   ============================================ */

/* Visually hidden but available to screen readers */
.sr-only {
  position: absolute;
  width: 1px;
  height: 1px;
  padding: 0;
  margin: -1px;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* Skip to main content link */
.skip-link {
  position: absolute;
  top: -40px;
  left: 0;
  background: var(--digicert-blue);
  color: var(--digicert-white);
  padding: var(--space-s) var(--space-m);
  z-index: 100;
}

.skip-link:focus {
  top: 0;
}

/* ============================================
   DISCLAIMER
   Shared "legal fine print" styling applied by
   applyDisclaimer() to the last content element
   of blocks with the `with-disclaimer` variant.
   ============================================ */
.disclaimer {
  font-size: var(--body-font-size-xxs);
  font-weight: var(--font-weight-regular);
  line-height: var(--line-height-normal);
}

/* ============================================
   CONTENT ANCHORS
   Universal invisible scroll-targets created by
   utils/anchors.js from {{anchor:Label}} tokens
   (linkable on any page; the document-reader
   template additionally builds a TOC from them).
   ============================================ */
.content-anchor {
  position: relative; /* containing block for the focus caret below */
  display: block;
  width: 0;
  height: 0;
  overflow: hidden;
  scroll-margin-top: calc(var(--header-height) + var(--space-l));
}

/* Mid-paragraph token: inline so it never breaks the line of prose. */
.content-anchor-inline {
  display: inline;
}

/* Visible focus indicator after a keyboard TOC jump. The anchor is a 0×0 hidden scroll
   hook, so focus would otherwise have no visible ring. On focus, show a short accent bar
   at the anchor's position (the section start you just landed on), escaping the 0×0 clip.
   :focus (not :focus-visible) is safe here — the anchor is tabindex="-1", only ever focused
   programmatically by the reader's goTo, which now moves focus for keyboard activation
   only. position:absolute → no layout shift. */
.content-anchor:focus {
  overflow: visible;
  outline: none;
}

.content-anchor:focus::after {
  content: "";
  position: absolute;
  left: 0;
  top: 0;
  width: 3px;
  height: 1.5em;
  border-radius: 2px;
  background: var(--link-color);
}

/* Windows High Contrast forces author backgrounds away, so the caret's fill would vanish
   (leaving a keyboard user no focus indicator after a TOC jump). A system color is honored
   in forced-colors and Highlight reads as a focus/selection cue. */
@media (forced-colors: active) {
  .content-anchor:focus::after {
    background: Highlight;
  }
}

/* ============================================
   DOCUMENT-READER CONTENT CLEARANCE (eager)
   Loaded eagerly (head.html) so the rail-clearance padding is present at FIRST PAINT: the
   panel widget CSS (styles/document-reader.css) is lazy, and applying this clearance only
   when that lands shifts the whole content column right on cold-cache reader pages (CLS).
   Only the content-side clearance lives here; the panel's own styling stays with the block.
   ============================================ */
body.document-reader {
  --document-reader-collapsed-w: 44px; /* collapsed rail width — fits Firefox's ~11px scrollbar */
  --document-reader-rail-space: calc(var(--document-reader-collapsed-w) + var(--space-xs));
}

/* Push reader content clear of the collapsed rail, but only by the part the centered
   container's own margin doesn't already absorb: from ~1360px up ((100% - container)/2 >=
   rail-space) the rail sits in the empty margin, so the formula self-resets to
   container-padding (no separate >=1600 rule needed) and never shoves the column off-center. */
body.document-reader main > .section > div {
  padding-left: calc(
    var(--container-padding)
    + max(0px, var(--document-reader-rail-space) - max(0px, (100% - var(--container-max-width)) / 2))
  );
}

/* Full-bleed blocks set their own padding (hero bg-image: padding:0; full-width logos:
   padding:unset) and out-specify the rule above, so the rail would overlap their left
   content. Re-clear them: the hero via its own --content-inset token (all widths — the
   max() picks centering when the gutter is wide, rail-clearance when narrow); the logos
   wrapper via padding-left that falls to 0 once the gutter absorbs the rail (mobile clears
   the edge rail; the rarer >=1600 floating-rail-over-full-width case is left to a ticket). */
body.document-reader .hero.bg-image,
body.document-reader .hero-event-beta.bg-image {
  --content-inset: max(
    var(--container-padding),
    calc((100% - var(--container-max-width)) / 2 + var(--container-padding)),
    calc(var(--container-padding) + max(0px, var(--document-reader-rail-space) - max(0px, (100% - var(--container-max-width)) / 2)))
  );
}

body.document-reader main > .section.full-width > .logos-wrapper {
  padding-left: max(0px, var(--document-reader-rail-space) - max(0px, (100% - var(--container-max-width)) / 2));
}

/* Default (non-bg-image) hero is a third case: not full-bleed, but its own wrapper padding
   (main > .section.hero-container > .hero-wrapper, blocks/hero/hero.css) out-specifies the
   generic clearance rule above, so the collapsed rail overlaps the headline. Re-assert the
   identical self-resetting term at winning specificity (resets to container-padding once the
   gutter absorbs the rail). bg-image heroes are unaffected — their :has() full-width wrapper
   rule out-specifies this and they clear via --content-inset. */
body.document-reader main > .section.hero-container > .hero-wrapper {
  padding-left: calc(
    var(--container-padding)
    + max(0px, var(--document-reader-rail-space) - max(0px, (100% - var(--container-max-width)) / 2))
  );
}

/* document-reader-beneath-hero: the TOC rides below the hero and never sits beside it, so the
   hero needs no rail clearance — reset it to its normal (non-reader) insets. Body sections keep
   their clearance (the rail still sits beside them once it rises). The toggle class is set on
   <body> at the reader module's import-time init — before `document-reader` is added — so this is
   active from the first frame the reader clearance exists (no CLS). */
body.document-reader.document-reader-beneath-hero main > .section.hero-container > .hero-wrapper {
  padding-left: var(--container-padding);
}

body.document-reader.document-reader-beneath-hero .hero.bg-image,
body.document-reader.document-reader-beneath-hero .hero-event-beta.bg-image {
  --content-inset: max(
    var(--container-padding),
    calc((100% - var(--container-max-width)) / 2 + var(--container-padding))
  );
}

/* Author guards (preview/dev only): utils/anchors.js adds html.content-anchor-debug when
   it detects an authoring problem, revealing a warning badge at the spot. Inert in
   production (the class is never added there). The variants:
   - [data-duplicate]   reused label — the page still works (ids deduped); rename it
   - [data-stripped]    fragment anchor removed on a reader page (TOC is page-authored only)
   - [data-empty-label] {{anchor:   }} — works as a target, can never join the TOC
   - .content-anchor-survivor  host of literal token text that survived normalization
     (typo'd keyword, single braces, unterminated, split by inline formatting, in a block)

   utils/section-autotab.js adds html.tab-style-debug for the same purpose, on the same
   shouldDebug() gate (utils/author-debug.js). It marks the SECTION rather than a content
   node, because the mistake is section-level configuration with nothing in the content to
   point at:
   - [data-tab-style-orphan]   a tab-style-* skin on a section that isn't tabbed at all
   - [data-tab-style-unknown]  a tab-style-* value that isn't a recognized skin (typo)
   These badge on ::before, not ::after: `main > .section` is a flex column, so ::before is
   the first flex item and lands at the TOP of the section, next to where the Section
   Metadata table was — an ::after would render at the section's bottom edge, potentially a
   screenful away. The badge therefore adds its own height plus the section gap; that layout
   shift is intentional and preview-only. Do NOT "fix" it with position:absolute — that would
   require position:relative on the section, which is the complication in-flow avoids. */
html.content-anchor-debug .content-anchor[data-duplicate],
html.content-anchor-debug .content-anchor[data-stripped],
html.content-anchor-debug .content-anchor[data-empty-label] {
  display: inline;
  width: auto;
  height: auto;
  overflow: visible;
}

html.content-anchor-debug .content-anchor[data-duplicate],
html.content-anchor-debug .content-anchor-survivor {
  outline: 2px dashed var(--digicert-red);
}

/* No var() fallbacks: tokens.css always loads with this file, and hardcoded copies of
   token values silently go stale if the token changes. */
html.content-anchor-debug .content-anchor[data-duplicate]::after,
html.content-anchor-debug .content-anchor[data-stripped]::after,
html.content-anchor-debug .content-anchor[data-empty-label]::after,
html.content-anchor-debug .content-anchor-survivor::after {
  display: inline-block;
  margin-inline-start: var(--space-xs);
  padding: 0 var(--space-xs);
  border-radius: var(--border-radius-xs);
  background: var(--digicert-red);
  color: var(--digicert-white);
  font-size: var(--body-font-size-xxs);
  font-weight: 700;
  white-space: nowrap;
}

html.content-anchor-debug .content-anchor[data-duplicate]::after {
  content: "⚠ duplicate anchor → #" attr(id);
}

html.content-anchor-debug .content-anchor[data-stripped]::after {
  content: "⚠ anchor removed (reader page): " attr(data-label);
}

html.content-anchor-debug .content-anchor[data-empty-label]::after {
  content: "⚠ empty anchor label → #" attr(id);
}

html.content-anchor-debug .content-anchor-survivor::after {
  content: "⚠ unprocessed anchor token";
}

/* Section-level author badges. `margin-inline` because the content inset comes from
   `main > .section > div` (sections.css) and matches div children only — a pseudo-element
   gets none of it and would otherwise sit flush against the viewport edge. */
html.tab-style-debug .section[data-tab-style-orphan]::before,
html.tab-style-debug .section[data-tab-style-unknown]::before {
  align-self: flex-start;
  margin-inline: var(--container-padding);
  padding: 0 var(--space-xs);
  border-radius: var(--border-radius-xs);
  background: var(--digicert-red);
  color: var(--digicert-white);
  font-size: var(--body-font-size-xxs);
  font-weight: 700;
}

html.tab-style-debug .section[data-tab-style-orphan]::before {
  content: "⚠ " attr(data-tab-style-orphan) " has no effect — section is missing tabbed-blocks";
}

html.tab-style-debug .section[data-tab-style-unknown]::before {
  content: "⚠ unknown tab style: " attr(data-tab-style-unknown) " — falling back to horizontal";
}

/* ============================================
   REDUCED MOTION
   Respects user preference for reduced motion
   ============================================ */
@media (prefers-reduced-motion: reduce) {
  .fade-in,
  .fade-up,
  .fade-left,
  .fade-right {
    transition: none;
    transform: none;
    opacity: 1;
  }
}
