/*! dp-mobile.css 1.1.0 (2026-10-06). Shared phone behaviour for the Drees suite.
   Pairs with dp-mobile.js. Vendored byte-identical into every app (sha256 of the pair: VERSION.txt).
   Spec: _qa/_job/design/SPEC.md.

   Every class starts with dpm- (state classes too: dpm-is-*) and does nothing until an app uses it. No colour here comes from
   a brand kit: each one reads a custom property with a safe fallback, so the same bytes work on
   the navy apps and on Club Hub's light and dark themes. Set these where the app's own tokens
   live (all optional):

     --dpm-surface   fill of the pull-to-refresh disc            fallback #18243F
     --dpm-line      1px edge of that disc                        fallback white 30%
     --dpm-ink       its mark when armed or refreshing            fallback #FFFFFF
     --dpm-ink-2     its mark while being pulled                  fallback #B8C2D0
     --dpm-skel      skeleton bone colour                         fallback 12% of the text colour
     --dpm-radius    corner of a skeleton tile                    fallback 12px
     --dpm-ptr-top   where the disc hangs from on a document scroller (bottom edge of the top bar)
     --dpm-ptr-z     z-index of the disc                          fallback 30
*/

/* ------------------------------------------------------------------ a. pull to refresh */

/* The helper replaces the browser's own pull: DPMobile.pullToRefresh puts this on <html> and on the
   scroller it is given. Y axis only: the X axis must stay free or the browser's sideways back swipe
   goes with it. */
.dpm-ptr-on { overscroll-behavior-y: none; }

/* A 36px disc that hangs under the top bar. Fixed, so nothing in the page moves for it. */
.dpm-ptr {
  position: fixed;
  top: var(--dpm-ptr-top, env(safe-area-inset-top, 0px));
  left: 50%;
  z-index: var(--dpm-ptr-z, 30);
  box-sizing: border-box;
  width: 36px;
  height: 36px;
  display: grid;
  place-items: center;
  border-radius: 50%;
  background: var(--dpm-surface, #18243F);
  border: 1px solid var(--dpm-line, rgba(255, 255, 255, 0.3));
  color: var(--dpm-ink-2, #B8C2D0);
  opacity: 0;
  pointer-events: none;
  transform: translate3d(-50%, -36px, 0);
  transition: transform 180ms ease-out, opacity 180ms ease-out;
}
.dpm-ptr.dpm-is-pulling { transition: none; }
.dpm-ptr.dpm-is-armed,
.dpm-ptr.dpm-is-busy { color: var(--dpm-ink, #FFFFFF); }
.dpm-ptr__ring {
  box-sizing: border-box;
  width: 16px;
  height: 16px;
  border-radius: 50%;
  border: 2.5px solid currentColor;
  border-top-color: transparent;
  transform: rotate(var(--dpm-ptr-turn, 0deg));
}
.dpm-ptr.dpm-is-busy .dpm-ptr__ring { animation: dpm-spin 700ms linear infinite; }
@keyframes dpm-spin { to { transform: rotate(360deg); } }
@media (prefers-reduced-motion: reduce) {
  .dpm-ptr { transition: none; }
  .dpm-ptr.dpm-is-busy .dpm-ptr__ring { animation: none; border-top-color: currentColor; }
}

/* ------------------------------------------------------------------ c. keyboard
   DPMobile.keyboard.init() sets --dpm-kb (keyboard height) and --dpm-vvh (visible height) on <html>
   and adds .dpm-kb-open. Use the two declarations in your own sheet / footer rule, or these classes. */

/* These four are single-purpose overrides, so they are !important: they must beat the rule that
   pinned the element in the first place, whatever its selector. */
.dpm-kb-bottom { bottom: var(--dpm-kb, 0px) !important; }       /* a fixed footer, or a sheet pinned to the bottom */
.dpm-kb-maxh { max-height: var(--dpm-vvh, 100dvh) !important; } /* nothing taller than what is visible */
.dpm-kb-pad { padding-bottom: max(env(safe-area-inset-bottom, 0px), var(--dpm-kb, 0px)) !important; } /* a full-screen layer that bottom-aligns its sheet */
html.dpm-kb-open .dpm-kb-safe { padding-bottom: 0 !important; } /* the keyboard already covers the home indicator */

/* A bottom sheet whose buttons stay above the keyboard: the sheet is the visible height at most,
   the body scrolls inside it, the footer is a flex child (never a second fixed layer).
   Structure only; the app's own sheet class supplies the look. */
.dpm-sheet {
  position: fixed;
  top: auto;
  left: 0;
  right: 0;
  bottom: var(--dpm-kb, 0px);
  box-sizing: border-box;
  width: min(100%, 640px);
  max-width: 100%;
  max-height: var(--dpm-vvh, 100dvh);
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  padding-bottom: env(safe-area-inset-bottom, 0px);
  overscroll-behavior: contain;
}
.dpm-sheet[hidden],
dialog.dpm-sheet:not([open]) { display: none; }
html.dpm-kb-open .dpm-sheet { padding-bottom: 0; }
.dpm-sheet__body {
  flex: 1 1 auto;
  min-height: 0;
  overflow-y: auto;
  overscroll-behavior: contain;
  -webkit-overflow-scrolling: touch;
}
.dpm-sheet__foot { flex: none; }

/* iOS zooms the page when a focused field is under 16px. Phones and tablets only: a mouse keeps
   the app's own (smaller) field size. Put .dpm-fields on <body> or on a form. */
@media (hover: none) and (pointer: coarse) {
  .dpm-fields input:not([type="checkbox"]):not([type="radio"]):not([type="range"]):not([type="file"]),
  .dpm-fields select,
  .dpm-fields textarea { font-size: max(16px, 1em); }
}

/* ------------------------------------------------------------------ d. skeletons (CSS only)
   A bone takes its size from what it stands in for, so nothing moves when the data lands:
     .dpm-skel                on the real element (keeps the real class, so the real size); its text and children are hidden
     .dpm-skel--line          an empty element exactly one text line tall in the font it sits in
     .dpm-skel--text          around placeholder words: the bone is as long as the words
     .dpm-skel--circle        avatar (--dpm-size, 40px)
     .dpm-skel--block         fills its container's width (a chart area, an image)
     .dpm-skel--tile          a whole tile (--dpm-h, 72px; corner --dpm-radius)
   Wrap them in .dpm-skel-group: it holds its space but stays invisible for the first 200ms, so a
   fast load never shows a skeleton at all.
   Bones are still. .dpm-skel-pulse on the group (or on one bone) adds a slow opacity pulse; there is
   no sweep and no shimmer, and reduced motion turns the pulse off. */

.dpm-skel,
.dpm-skel--line::after { --dpm-bone: var(--dpm-skel, rgba(128, 134, 148, 0.26)); }
@supports (background-color: color-mix(in srgb, red 10%, blue)) {
  .dpm-skel,
  .dpm-skel--line::after { --dpm-bone: var(--dpm-skel, color-mix(in srgb, currentColor 12%, transparent)); }
}
.dpm-skel {
  box-sizing: border-box;
  border-radius: var(--dpm-skel-radius, 6px);
  background-color: var(--dpm-bone);
  -webkit-text-fill-color: transparent;
  -webkit-user-select: none;
  user-select: none;
  pointer-events: none;
}
.dpm-skel > * { visibility: hidden; }
.dpm-skel-pulse .dpm-skel,
.dpm-skel.dpm-skel-pulse { animation: dpm-skel-pulse 1.6s ease-in-out infinite; }
.dpm-skel--line {
  display: block;
  position: relative;
  width: var(--dpm-w, 100%);
  background-color: transparent;
}
.dpm-skel--line::before { content: "\00a0"; }
.dpm-skel--line::after {
  content: "";
  position: absolute;
  left: 0;
  right: 0;
  top: 50%;
  height: 0.72em;
  margin-top: -0.36em;
  border-radius: var(--dpm-skel-radius, 6px);
  background-color: var(--dpm-bone);
}
.dpm-skel--text {
  display: inline;
  border-radius: 4px;
  -webkit-box-decoration-break: clone;
  box-decoration-break: clone;
}
.dpm-skel--circle {
  display: block;
  flex: none;
  width: var(--dpm-size, 40px);
  height: var(--dpm-size, 40px);
  border-radius: 50%;
}
.dpm-skel--block { display: block; width: 100%; }
.dpm-skel--tile {
  display: block;
  min-height: var(--dpm-h, 72px);
  border-radius: var(--dpm-radius, 12px);
}
/* Widths for lines without an inline style (some apps send a strict style-src). */
.dpm-w-20 { --dpm-w: 20%; }
.dpm-w-30 { --dpm-w: 30%; }
.dpm-w-40 { --dpm-w: 40%; }
.dpm-w-50 { --dpm-w: 50%; }
.dpm-w-60 { --dpm-w: 60%; }
.dpm-w-70 { --dpm-w: 70%; }
.dpm-w-80 { --dpm-w: 80%; }
.dpm-w-90 { --dpm-w: 90%; }

.dpm-skel-group { animation: dpm-skel-in 1ms linear var(--dpm-skel-delay, 200ms) both; }
.dpm-skel-group.dpm-skel-now { animation: none; }
@keyframes dpm-skel-in { from { opacity: 0; } to { opacity: 1; } }
@keyframes dpm-skel-pulse { 50% { opacity: 0.55; } }
@media (prefers-reduced-motion: reduce) {
  .dpm-skel-pulse .dpm-skel,
  .dpm-skel.dpm-skel-pulse { animation: none; }
}

/* ------------------------------------------------------------------ e. swipes */

/* DPMobile.swipe(el) adds this: up and down still scrolls, left and right is ours. */
.dpm-swipe { touch-action: pan-y; }

/* A row of cards that pages sideways with the next card peeking in. The browser does the drag. */
.dpm-snap {
  display: flex;
  gap: var(--dpm-snap-gap, 12px);
  overflow-x: auto;
  overflow-y: hidden;
  scroll-snap-type: x mandatory;
  scroll-padding-inline: var(--dpm-snap-pad, 16px);
  padding-inline: var(--dpm-snap-pad, 16px);
  overscroll-behavior-x: contain;
  -webkit-overflow-scrolling: touch;
  scrollbar-width: none;
}
.dpm-snap::-webkit-scrollbar { display: none; }
.dpm-snap > * {
  flex: 0 0 var(--dpm-snap-w, 85%);
  min-width: 0;
  scroll-snap-align: start;
}
.dpm-snap--stop > * { scroll-snap-stop: always; } /* days and weeks: a fling must not skip one */
@media (prefers-reduced-motion: reduce) {
  .dpm-snap { scroll-behavior: auto; }
}

/* ------------------------------------------------------------------ f. screen change
   DPMobile.transition(fn) puts .dpm-vt on <html> for the length of the change. A quick cross-fade,
   nothing travels. Same-document only: there is no rule here for full page loads on purpose. */
html.dpm-vt::view-transition-old(root),
html.dpm-vt::view-transition-new(root) {
  animation-duration: 180ms;
  animation-timing-function: cubic-bezier(0.2, 0, 0, 1);
}
@media (prefers-reduced-motion: reduce) {
  html.dpm-vt::view-transition-group(*),
  html.dpm-vt::view-transition-old(*),
  html.dpm-vt::view-transition-new(*) { animation: none !important; }
}

/* ------------------------------------------------------------------ helpers */
.dpm-vh {
  position: absolute;
  width: 1px;
  height: 1px;
  margin: -1px;
  padding: 0;
  overflow: hidden;
  clip: rect(0 0 0 0);
  white-space: nowrap;
  border: 0;
}
