/* ============================================================================
   NextShot — base
   styles/base.css

   Load order: tokens.css -> base.css -> components.css

   Owns: the reset, the app shell (nav bar / scroll region / tab bar / overlay
   layers), the type ramp utilities, the press-feedback primitive, the paper
   grain, and the reduced-motion neutraliser.

   RULES OBSERVED THROUGHOUT
   - Every colour, radius, shadow, duration, easing, size and space is a
     var(--ns-*) from tokens.css. There are no literal hexes, no literal
     radii, no literal shadows, no literal spacing values.
   - Fonts are system / SF Pro only. No script faces, no italics.
   - Zero transform: rotate() on any UI element.
   - Zero multi-value or percentage border-radius.
   ========================================================================== */

/* ==========================================================================
   0. LOCAL PRIMITIVES
   The only new custom properties this project declares. They are layering
   order and the desktop courtesy frame — neither is a design token, both are
   structural constants that belong with the shell that uses them.
   ========================================================================== */

:root {
  /* Stacking order. Nothing may set a bare numeric z-index; use these.
     Chrome (navbar/tabbar) stays under capture/sheet/toast so modals own
     the foreground — iOS sheet presentation. Grain is a non-interactive
     texture over chrome+content but under logged celebrations. */
  --ns-z-base:    0;
  --ns-z-content: 1;
  --ns-z-sticky:  20;
  --ns-z-navbar:  40;
  --ns-z-tabbar:  45;
  --ns-z-capture: 60;
  --ns-z-sheet:   70;
  --ns-z-toast:   80;
  --ns-z-grain:   90;
  --ns-z-logged:  99;

  /* Desktop courtesy frame. 430pt derived from the 390pt reference width
     plus one 40pt step, i.e. an iPhone Pro Max class viewport. */
  --ns-frame-w: calc(var(--ns-screen-w) + var(--ns-sp-10));
  --ns-frame-ratio: 9 / 19.5;
}

/* ==========================================================================
   1. RESET
   Modern, minimal. tokens.css already wires html/body colour + type; this
   only adds what the shell depends on.
   ========================================================================== */

*,
*::before,
*::after {
  box-sizing: border-box;
}

html,
body {
  height: 100%;
  margin: 0;
  padding: 0;
  /* Kills the iOS document rubber-band. Required for the installed PWA to
     stop feeling like a web page inside a web view. */
  overscroll-behavior: none;
}

html {
  /* Prevents the horizontal drift that a full-bleed grain layer can cause. */
  overflow-x: hidden;
}

body {
  min-height: 100dvh;
  overflow: hidden;
  -webkit-tap-highlight-color: transparent;
  -webkit-touch-callout: none;
  text-size-adjust: 100%;
  -webkit-text-size-adjust: 100%;
}

/* ==========================================================================
   1b. MOBILE DOCUMENT SCROLL (Safari chrome collapse)
   Safari only hides its bottom URL/search bar when the *document* scrolls.
   An absolute inner .ns-scroll with overflow keeps that bar stuck — bad for
   screenshots. On phone widths, promote scrolling to the document and pin
   chrome/overlays with position:fixed so they still track the viewport.
   Desktop (≥480px) keeps the framed inner-scroll shell unchanged.
   ========================================================================== */

@media (max-width: 479px) {
  html,
  body {
    height: auto;
    min-height: 100%;
    overflow-x: hidden;
    overflow-y: auto;
    /* Allow the natural scroll that drives Safari’s chrome collapse. */
    overscroll-behavior-y: auto;
  }

  body {
    overflow-y: auto;
    overflow-x: hidden;
  }

  .ns-app {
    height: auto;
    min-height: 100dvh;
    overflow: visible;
  }

  .ns-scroll {
    position: relative;
    inset: auto;
    overflow: visible;
    -webkit-overflow-scrolling: auto;
    overscroll-behavior-y: auto;
  }

  /* Pin chrome to the visual viewport while the document scrolls. */
  .ns-navbar,
  .ns-tabbar {
    position: fixed;
  }

  .ns-layer,
  .ns-grain {
    position: fixed;
  }
}

/* Block-level media, never overflowing its column. */
img,
svg,
video,
canvas,
picture {
  display: block;
  max-width: 100%;
}

img,
video {
  height: auto;
}

/* Controls inherit the brand type instead of falling back to a system UI
   font. This single rule is what stops SF Pro leaking in through inputs. */
button,
input,
textarea,
select {
  font: inherit;
  letter-spacing: inherit;
  color: inherit;
  margin: 0;
}

button {
  background: none;
  border: 0;
  padding: 0;
  cursor: pointer;
  -webkit-tap-highlight-color: transparent;
  /* Removes the 300ms synthetic click delay without disabling scroll. */
  touch-action: manipulation;
  -webkit-appearance: none;
  appearance: none;
  color: inherit;
  text-align: inherit;
}

button:disabled {
  cursor: default;
}

input,
textarea {
  -webkit-appearance: none;
  appearance: none;
  border: 0;
  background: none;
  border-radius: 0; /* reset only — every real radius comes from the scale */
}

textarea {
  resize: none;
}

a {
  color: var(--ns-tint);
  text-decoration: none;
  -webkit-tap-highlight-color: transparent;
}

ul,
ol {
  list-style: none;
  margin: 0;
  padding: 0;
}

h1, h2, h3, h4, h5, h6, p, figure, blockquote {
  margin: 0;
  font-size: inherit;
  font-weight: inherit;
}

hr {
  border: 0;
  height: var(--ns-hairline);
  background: var(--ns-separator);
  margin: 0;
}

/* Only show a focus ring for keyboard users. */
:focus {
  outline: none;
}

:focus-visible {
  outline: none;
  box-shadow: var(--ns-focus-ring);
}

/* ==========================================================================
   2. SCROLLING
   Hidden scrollbars that still scroll, momentum on iOS, and scroll
   containment so a sheet's inner scroll never chains into the page.
   ========================================================================== */

.ns-scrollable {
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.ns-scrollable-x {
  overflow-x: auto;
  overflow-y: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-x: contain;
  scroll-snap-type: x proximity;
  scrollbar-width: none;
  -ms-overflow-style: none;
}

.ns-scrollable::-webkit-scrollbar,
.ns-scrollable-x::-webkit-scrollbar {
  width: 0;
  height: 0;
  display: none;
}

/* ==========================================================================
   3. APP SHELL
   .ns-app is the device. It is a fixed-height flex column that owns three
   absolutely positioned planes: the nav bar at the top, the scroll region
   filling the middle, the tab bar at the bottom. Overlay layers and the
   grain sit above all three.

   The bars are absolute (not fixed) so the desktop frame in §4 contains
   them correctly. Inside the frame they behave identically to fixed.
   ========================================================================== */

.ns-app {
  position: relative;
  width: 100%;
  height: 100dvh;
  margin: 0 auto;
  display: flex;
  flex-direction: column;
  overflow: hidden;
  background: var(--ns-background);
  color: var(--ns-label);
  /* Contains the grain blend and any backdrop-filter to the app itself. */
  isolation: isolate;
}

/* Screens that present a grouped list (journey, composer host) opt into the
   darker ground by setting data-ground on the app root or on .ns-scroll. */
.ns-app[data-ground="grouped"],
.ns-scroll[data-ground="grouped"] {
  background: var(--ns-grouped-background);
}

/* --- the scroll region ---------------------------------------------------
   Sits between the two bars. Padding, not margin, so content scrolls UNDER
   the translucent chrome — that is the whole point of the material. */

.ns-scroll {
  position: absolute;
  inset: 0;
  z-index: var(--ns-z-content);
  overflow-y: auto;
  overflow-x: hidden;
  -webkit-overflow-scrolling: touch;
  overscroll-behavior-y: contain;
  scrollbar-width: none;
  -ms-overflow-style: none;
  scroll-behavior: smooth;
}

.ns-scroll::-webkit-scrollbar {
  width: 0;
  display: none;
}

/* --- the padded content column ------------------------------------------
   .ns-screen is the direct child of .ns-scroll. Its top padding clears the
   nav bar, its bottom padding clears the tab bar. */

.ns-screen {
  display: flex;
  flex-direction: column;
  min-height: 100%;
  padding-top: var(--ns-navbar-total);
  padding-right: calc(var(--ns-gutter) + var(--ns-safe-right));
  padding-bottom: var(--ns-scroll-pad-bottom);
  padding-left: calc(var(--ns-gutter) + var(--ns-safe-left));
}

/* A screen whose nav bar carries a large title needs no extra top padding —
   the large title itself lives in the scroll flow directly beneath the
   compact bar. This modifier exists for screens that DO NOT render an
   .ns-largetitle but still want the large-title amount of headroom. */
.ns-screen--large {
  padding-top: var(--ns-navbar-large-total);
}

/* A screen with no tab bar (frames, capture) reclaims the bottom. */
.ns-screen--notabbar {
  padding-bottom: calc(var(--ns-safe-bottom) + var(--ns-sp-6));
}

/* Chromeless screens (splash, capture): only safe-area insets, no nav/tab
   chrome offsets. Screens that paint their own chrome use this. */
.ns-screen--nochrome {
  padding-top: var(--ns-safe-top);
  padding-bottom: var(--ns-safe-bottom);
}

/* A screen that manages its own gutters (edge-to-edge media). */
.ns-screen--flush {
  padding-right: var(--ns-safe-right);
  padding-left: var(--ns-safe-left);
}

/* Vertical rhythm between blocks inside a screen. */
.ns-section {
  margin-top: var(--ns-sp-8);
}

.ns-section--tight {
  margin-top: var(--ns-sp-6);
}

.ns-section--loose {
  margin-top: var(--ns-sp-10);
}

.ns-section:first-child {
  margin-top: var(--ns-sp-0);
}

/* --- overlay layers ------------------------------------------------------
   Full-bleed hosts for anything that escapes the scroll region. app.js
   appends into these; it never sets a z-index itself. */

.ns-layer {
  position: absolute;
  inset: 0;
  z-index: var(--ns-z-content);
  pointer-events: none;
}

.ns-layer > * {
  pointer-events: auto;
}

.ns-layer--capture { z-index: var(--ns-z-capture); }
.ns-layer--sheet   { z-index: var(--ns-z-sheet); }
.ns-layer--toast   { z-index: var(--ns-z-toast); }

/* A layer with nothing presented takes no hits at all. */
.ns-layer:empty {
  display: none;
}

/* ==========================================================================
   4. DESKTOP COURTESY FRAME
   Above 480px, centre the app in a clean phone-shaped frame on the grouped
   ground. Token radius, token elevation, one hairline ring. No bezel art,
   no wobble border, no hard offset shadow.
   ========================================================================== */

@media (min-width: 480px) {
  /* Desktop browsers DO support env(safe-area-inset-*) and resolve it to
     0px, so the fallbacks declared in tokens.css never fire and the frame
     loses its notch and home-indicator insets. Re-assert those same
     documented values so the frame simulates the device honestly.

     This MUST be declared on :root, not on .ns-app. The derived tokens
     (--ns-navbar-total, --ns-tabbar-total, --ns-scroll-pad-bottom,
     --ns-sheet-max-h) substitute var(--ns-safe-*) at the element where
     they are DECLARED — :root — and then inherit the already-resolved
     length. Overriding lower in the tree changes the paddings that read
     --ns-safe-* directly but leaves every derived total stale, which
     collapses the tab bar's content box. */
  :root {
    --ns-safe-top: 47px;
    --ns-safe-bottom: 34px;
    --ns-safe-left: 0px;
    --ns-safe-right: 0px;
  }

  html,
  body {
    background: var(--ns-grouped-background);
  }

  body {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: var(--ns-sp-6);
  }

  .ns-app {
    width: var(--ns-frame-w);
    height: auto;
    aspect-ratio: var(--ns-frame-ratio);
    max-height: calc(100dvh - var(--ns-sp-12));
    border-radius: var(--ns-r-2xl);
    box-shadow: var(--ns-shadow-4);
    /* A single hairline ring instead of a drawn bezel. */
    outline: var(--ns-hairline) solid var(--ns-separator);
    outline-offset: calc(var(--ns-hairline) * -1);
  }

  /* No simulated status bar / Dynamic Island / home indicator — ever.
     Screen recordings and portfolio demos must not show fake iOS chrome.
     Safe-area insets above still reserve real-device spacing. */

  @supports (corner-shape: squircle) {
    .ns-app {
      corner-shape: var(--ns-corner-shape);
    }
  }
}

/* Short desktop windows: drop the frame chrome rather than crush the app. */
@media (min-width: 480px) and (max-height: 640px) {
  body {
    padding: var(--ns-sp-0);
  }

  .ns-app {
    height: 100dvh;
    max-height: 100dvh;
    aspect-ratio: auto;
    border-radius: 0; /* frame chrome removed, not restyled */
    box-shadow: none;
    outline: none;
  }
}

/* ==========================================================================
   5. TYPE UTILITIES
   One class per step of the ramp. Each sets `font` first (the shorthand
   resets line-height and font-variant) and then its tracking, exactly as the
   token file prescribes.

   Hierarchy is weight + size. Never italic. Section/micro may be uppercase.
   ========================================================================== */

.ns-display-xl {
  font: var(--ns-type-display-xl);
  letter-spacing: var(--ns-tracking-display);
}

.ns-display-lg {
  font: var(--ns-type-display-lg);
  letter-spacing: var(--ns-tracking-display);
}

.ns-display-md {
  font: var(--ns-type-display-md);
  letter-spacing: var(--ns-tracking-display);
}

.ns-display-sm,
.ns-title {
  font: var(--ns-type-display-sm);
  letter-spacing: var(--ns-tracking-display);
}

.ns-numeral {
  font: var(--ns-type-numeral);
  letter-spacing: var(--ns-tracking-display);
  font-variant-numeric: lining-nums tabular-nums;
}

.ns-headline {
  font: var(--ns-type-headline);
  letter-spacing: var(--ns-tracking-headline);
}

.ns-body {
  font: var(--ns-type-body);
  letter-spacing: var(--ns-tracking-body);
}

.ns-callout {
  font: var(--ns-type-callout);
  letter-spacing: var(--ns-tracking-callout);
}

.ns-subhead {
  font: var(--ns-type-subhead);
  letter-spacing: var(--ns-tracking-subhead);
}

.ns-footnote {
  font: var(--ns-type-footnote);
  letter-spacing: var(--ns-tracking-footnote);
}

.ns-caption {
  font: var(--ns-type-caption);
  letter-spacing: var(--ns-tracking-caption);
}

.ns-caption-sm {
  font: var(--ns-type-caption-sm);
  letter-spacing: var(--ns-tracking-caption-sm);
}

.ns-tab-label {
  font: var(--ns-type-tab);
  letter-spacing: var(--ns-tracking-tab);
}

/* Tracked caps carry their trailing letter-space; the negative right margin
   pulls the optical edge back into alignment. */
.ns-caps {
  font: var(--ns-type-section);
  letter-spacing: var(--ns-tracking-section);
  text-transform: uppercase;
  margin-right: -0.14em;
}

.ns-micro {
  font: var(--ns-type-micro);
  letter-spacing: var(--ns-tracking-micro);
  text-transform: uppercase;
  margin-right: -0.14em;
}

/* --- text colour utilities ---------------------------------------------- */

.ns-text-primary     { color: var(--ns-label); }
.ns-text-secondary   { color: var(--ns-label-secondary); }
.ns-text-tertiary    { color: var(--ns-label-tertiary); }
.ns-text-quaternary  { color: var(--ns-label-quaternary); }
.ns-text-inverse     { color: var(--ns-label-inverse); }
.ns-text-tint        { color: var(--ns-tint); }
.ns-text-on-tint     { color: var(--ns-on-tint); }

/* --ns-label-tertiary is 2.8:1 — decorative or ≥19px only. At caption /
   micro / tab sizes, lift to secondary so contrast stays AA. */
.ns-caption.ns-text-tertiary,
.ns-caption-sm.ns-text-tertiary,
.ns-micro.ns-text-tertiary,
.ns-tab.ns-text-tertiary,
.ns-footnote.ns-text-tertiary {
  color: var(--ns-label-secondary);
}

/* --- text behaviour ------------------------------------------------------ */

.ns-truncate {
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  min-width: 0;
}

.ns-clamp-2 {
  display: -webkit-box;
  -webkit-line-clamp: 2;
  line-clamp: 2;
  -webkit-box-orient: vertical;
  overflow: hidden;
}

.ns-measure {
  max-width: 32ch;
}

.ns-center {
  text-align: center;
}

/* ==========================================================================
   6. LAYOUT UTILITIES
   Deliberately tiny. Anything more structural belongs in components.css.
   ========================================================================== */

.ns-row {
  display: flex;
  align-items: center;
  gap: var(--ns-sp-3);
  min-width: 0;
}

.ns-row--tight { gap: var(--ns-sp-2); }
.ns-row--loose { gap: var(--ns-sp-4); }
.ns-row--top   { align-items: flex-start; }

.ns-col {
  display: flex;
  flex-direction: column;
  gap: var(--ns-sp-2);
  min-width: 0;
}

.ns-col--tight { gap: var(--ns-sp-1); }
.ns-col--loose { gap: var(--ns-sp-4); }

.ns-spacer {
  flex: 1 1 auto;
  min-width: 0;
}

.ns-hidden {
  display: none;
}

/* Visible to assistive tech, invisible on screen. Used for the accessible
   labels on icon-only bar buttons and the trend-chart hit targets. */
.ns-sr-only {
  position: absolute;
  width: var(--ns-hairline);
  height: var(--ns-hairline);
  padding: var(--ns-sp-0);
  margin: calc(var(--ns-hairline) * -1);
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
  border: 0;
}

/* ==========================================================================
   7. PRESS FEEDBACK
   The single interaction primitive. Everything tappable gets .ns-pressable.
   Scale down fast on press, spring back on release. No rotation, ever.
   Reduced motion collapses --ns-press-scale to 1 at the token level, so the
   transform silently becomes a no-op without a second rule.
   ========================================================================== */

.ns-pressable {
  -webkit-tap-highlight-color: transparent;
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  transform: scale(1);
  transition:
    transform var(--ns-dur-base) var(--ns-ease-spring),
    opacity   var(--ns-dur-base) var(--ns-ease-standard),
    background-color var(--ns-dur-fast) var(--ns-ease-standard);
}

.ns-pressable:active {
  transform: scale(var(--ns-press-scale));
  transition-duration: var(--ns-dur-instant);
}

/* Full-width cards and list rows take the gentler scale. */
.ns-pressable--lg:active {
  transform: scale(var(--ns-press-scale-lg));
}

/* Bare text buttons and bar items dim instead of scaling. */
.ns-pressable--fade:active {
  transform: scale(1);
  opacity: var(--ns-press-opacity);
}

.ns-pressable:disabled,
.ns-pressable[aria-disabled="true"] {
  opacity: var(--ns-disabled-opacity);
  pointer-events: none;
}

/* ==========================================================================
   8. PAPER GRAIN
   Exactly one full-bleed layer per app, inside .ns-app so the isolate in §3
   scopes the blend. tokens.css already solves the dark-mode collapse: light
   multiplies a warm brown speck, dark screens a cool pale speck at reduced
   opacity, because multiply over navy turns to mud.
   ========================================================================== */

.ns-grain {
  position: absolute;
  inset: 0;
  z-index: var(--ns-z-grain);
  pointer-events: none;
  background-image: var(--ns-grain-image);
  background-repeat: repeat;
  mix-blend-mode: var(--ns-grain-blend);
  opacity: var(--ns-grain-opacity);
  transition: opacity var(--ns-dur-base) var(--ns-ease-standard);
  /* The image is a 180x180 turbulence tile; let it tile at native size. */
  background-size: auto;
}

/* Suppress over live camera and photo-first surfaces, where a speck reads as
   sensor noise rather than paper. app.js toggles this on the capture screen. */
.ns-grain--off {
  opacity: 0;
}

/* Very old engines without blend-mode support get a flat, quiet speck rather
   than an opaque brown sheet over the whole app. */
@supports not (mix-blend-mode: multiply) {
  .ns-grain {
    mix-blend-mode: normal;
    opacity: var(--ns-disabled-opacity);
  }
}

/* ==========================================================================
   9. SHARED KEYFRAMES
   Screen and overlay transitions used by app.js. Durations come from the
   tokens at the call site, never from the keyframe.
   ========================================================================== */

@keyframes ns-fade-in {
  from { opacity: 0; }
  to   { opacity: 1; }
}

@keyframes ns-fade-out {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Screen push: enters from slightly below and fades. Replaces pbpPage. */
@keyframes ns-screen-in {
  from { opacity: 0; transform: translate3d(0, var(--ns-sp-2), 0); }
  to   { opacity: 1; transform: translate3d(0, 0, 0); }
}

/* Sheet present. Replaces pbpRise; the curve is --ns-ease-spring. */
@keyframes ns-sheet-in {
  from { transform: translate3d(0, 100%, 0); }
  to   { transform: translate3d(0, 0, 0); }
}

/* Toast: rises, dwells, sinks. No rotation (the old pbpToast rotated -1deg
   for its whole life). */
@keyframes ns-toast-in {
  from { opacity: 0; transform: translate3d(0, var(--ns-sp-3), 0) scale(var(--ns-press-scale)); }
  to   { opacity: 1; transform: translate3d(0, 0, 0) scale(1); }
}

/* Shutter flash. 280ms at the call site, matching the bridge's finish delay. */
@keyframes ns-flash {
  from { opacity: 1; }
  to   { opacity: 0; }
}

/* Just-logged ink stamp on a calendar cell. Scale + fade only. */
@keyframes ns-stamp {
  0%   { opacity: 0; transform: scale(1.3); }
  55%  { opacity: 1; transform: scale(0.94); }
  100% { opacity: 1; transform: scale(1); }
}

/* Polaroid develop: a photo resolving from sepia. Brand, and free. */
@keyframes ns-develop {
  from { filter: sepia(0.55) saturate(0.6) contrast(0.9); opacity: 0.4; }
  to   { filter: none; opacity: 1; }
}

/* SVG stroke reveal for the hand-inked marks. Set --ns-draw-len per path. */
@keyframes ns-draw {
  from { stroke-dashoffset: var(--ns-draw-len, 200); }
  to   { stroke-dashoffset: 0; }
}

.ns-animate-in {
  animation: ns-screen-in var(--ns-dur-base) var(--ns-ease-decel) both;
}

.ns-develop {
  animation: ns-develop var(--ns-dur-sheet) var(--ns-ease-decel) both;
}

/* ==========================================================================
   10. REDUCED MOTION
   tokens.css already retunes the duration and press-scale variables, which
   covers everything written against them. This block is the backstop for
   keyframed animation and for any third-party CSS (lottie's injected
   styles) that does not read the tokens.
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
  *,
  *::before,
  *::after {
    animation-duration: var(--ns-dur-instant) !important;
    animation-delay: var(--ns-dur-instant) !important;
    animation-iteration-count: 1 !important;
    transition-duration: var(--ns-dur-fast) !important;
    transition-delay: 0s !important;
  }

  .ns-scroll,
  .ns-scrollable {
    scroll-behavior: auto;
  }

  /* Illustration that exists only to move stops moving and rests resolved. */
  .ns-develop,
  .ns-animate-in {
    animation: none;
    opacity: 1;
    filter: none;
    transform: none;
  }
}
