/**
 * PulsarCards — Design Tokens
 * ============================
 * The single source of truth for colour, type, space, depth and motion.
 * Every other stylesheet consumes these; none of them redeclare them.
 *
 * Brand story: a pulsar is a spinning neutron star whose magnetic field
 * fires a beam of electromagnetic radiation on a precise, unfailing period.
 * NFC moves data on the same physics. The palette follows that image —
 * deep ink, pressed paper, photon blue, and a gold beam for display type.
 * Portal and dashboard alias these names — they do not declare a second palette.
 *
 * Scales are deliberately short. A designer should be able to hold the whole
 * system in their head; that is what keeps 40 screens looking like one product.
 *
 * Cascade contract (legacy coexistence)
 * -------------------------------------
 * Layer order is reserved here so new work can opt into @layer without
 * fighting Bootstrap / elite / style.css. Existing surface sheets
 * (pulsar-dash, pulsar-site, pulsar-auth, pulsar-card) stay *unlayered*
 * so they keep winning on pages that already rely on them.
 *
 *   @layer pulsar.tokens → pulsar.primitives → pulsar.components → pulsar.surface
 *
 * New controls that would collide with Bootstrap (.modal, .nav-tabs,
 * .pagination, .toast) live in pulsar-components.css as .pc-* names.
 * Untouched pages simply do not link that file.
 */

@layer pulsar.tokens, pulsar.primitives, pulsar.components, pulsar.surface;

:root {
  /* ── Core brand ──────────────────────────────────────────────────
     Fixed identity values. These do not change per surface or theme. */
  --ink:            #161314;   /* marketing ink — headings, inverse bands  */
  --paper:          #FFFFFF;   /* marketing paper — page background        */
  --raised:         #F5F5F5;   /* elevated surface — cards, sheets         */
  --line:           rgba(35, 31, 32, 0.12);
  --muted:          #5C5758;   /* secondary text                           */
  --brand:          #1975B8;   /* photon — links, primary action           */
  --gold:           #C4A46A;   /* the beam — display accent, gold type     */
  --gold-ink:       #1A1408;   /* text that sits on gold                   */

  /* Tonal steps. Derived from the core hues so tints stay in-family
     instead of drifting toward grey. */
  --ink-90:         #1C191A;
  --ink-80:         #231F20;
  --ink-60:         #5C5758;
  --gold-strong:    #A8874A;
  --gold-soft:      #E4D2A8;
  --gold-wash:      rgba(196, 164, 106, 0.12);
  --brand-strong:   #125A8F;
  --brand-soft:     #4AA3DC;
  --brand-wash:     rgba(25, 117, 184, 0.14);

  /* ── Semantic state ─────────────────────────────────────────────
     Hue-separated so meaning never rests on saturation alone, and each
     pairs with a wash for low-emphasis backgrounds (WCAG AA on --raised). */
  --success:        #48D49F;
  --success-wash:   rgba(72, 212, 159, 0.12);
  --warning:        #9A6B12;
  --warning-wash:   rgba(154, 107, 18, 0.12);
  --danger:         #FF6B7A;
  --danger-wash:    rgba(255, 107, 122, 0.12);
  --info:           var(--brand);
  --info-wash:      var(--brand-wash);

  /* Portal/dashboard and marketing names. They alias this file —
     they are not a second palette. */
  --bg:             var(--paper);
  --bg-elev:        var(--raised);
  --text:           var(--ink-80);
  --subtle:         var(--muted);
  --line-strong:    rgba(35, 31, 32, 0.18);
  --cyan:           var(--brand);
  --violet:         var(--brand-strong);
  --amber:          var(--gold);
  --mint:           var(--success);
  --rose:           var(--danger);
  --accent:         var(--brand);
  --photon:         var(--brand);
  --photon-2:       var(--brand-soft);
  --accent-soft:    var(--brand-wash);
  --ink-2:          var(--ink-90);
  --ink-3:          var(--ink-80);
  --paper-2:        var(--raised);
  --paper-3:        #ECECED;
  --text-soft:      var(--muted);
  --text-on-dark:   var(--paper);
  --text-dark-soft: #B8B3B4;
  --line-dark:      rgba(255, 255, 255, 0.12);

  /* ── Surface roles ──────────────────────────────────────────────
     Components reference these, never the raw hues. Overriding this
     block is how a dark or branded context is created — see
     [data-surface] below — so components need no per-theme rules. */
  --surface-page:      var(--paper);
  --surface-raised:    var(--raised);
  --surface-sunken:    #ECECED;
  --surface-inverse:   var(--ink);
  --surface-line:      var(--line);
  /* Alias used by older dash selectors. Prefer --surface-raised in new CSS. */
  --surface:           var(--surface-raised);
  --text-primary:      var(--ink);
  --text-secondary:    var(--muted);
  --text-inverse:      var(--paper);
  --text-on-accent:    var(--gold-ink);

  /* ── Typography ─────────────────────────────────────────────────
     Fraunces is the loaded variable display face. Inter does interface
     work. --font-* aliases keep older marketing sheets on the same stack. */
  --display: Fraunces, "Iowan Old Style", Georgia, serif;
  --sans:    Inter, "Open Sans", system-ui, -apple-system, "Segoe UI", sans-serif;
  --ui:      var(--sans);
  --font-display: var(--display);
  --font-body:    var(--ui);
  --font-ui:      var(--ui);
  --mono:    ui-monospace, SFMono-Regular, "SF Mono", Menlo, monospace;

  /* Fluid scale, ~1.25 ratio. clamp() removes the need for per-breakpoint
     font-size overrides, which is where type systems usually rot. */
  /* 12px is the floor. Smaller uppercase micro-labels look tidy on a
     designer's monitor and fail on a phone at arm's length, so the scale
     simply does not offer anything below it. */
  --text-2xs:  0.75rem;                                /* 12px  */
  --text-xs:   0.75rem;                                /* 12px  */
  --text-sm:   0.875rem;                               /* 14px  */
  --text-base: 1rem;                                   /* 16px  */
  --text-lg:   1.125rem;                               /* 18px  */
  --text-xl:   clamp(1.25rem, 1.1rem + 0.6vw, 1.5rem);
  --text-2xl:  clamp(1.5rem,  1.3rem + 1.1vw, 2rem);
  --text-3xl:  clamp(1.875rem, 1.5rem + 1.8vw, 2.75rem);
  --text-4xl:  clamp(2.25rem, 1.6rem + 3vw,   3.75rem);
  --text-hero: clamp(2.5rem,  1.5rem + 5vw,   4.5rem);

  --weight-regular:  400;
  --weight-medium:   500;
  --weight-semibold: 600;
  --weight-bold:     700;

  /* Line height tightens as size grows — long measures need air,
     display lines need none. */
  --leading-tight:   1.1;
  --leading-snug:    1.25;
  --leading-normal:  1.5;
  --leading-relaxed: 1.65;

  --tracking-tight:  -0.02em;
  --tracking-normal: 0;
  --tracking-wide:   0.04em;
  --tracking-caps:   0.12em;

  /* Optimal reading measure. Longer lines cost the eye its return sweep. */
  --measure:       68ch;
  --measure-tight: 46ch;

  /* ── Space — strict 8px grid, 4px only for icon insets ──────────
     Named by step, not by use, so the same rhythm serves any component. */
  --space-0:  0;
  --space-1:  0.25rem;   /*  4px */
  --space-2:  0.5rem;    /*  8px */
  --space-3:  0.75rem;   /* 12px */
  --space-4:  1rem;      /* 16px */
  --space-5:  1.5rem;    /* 24px */
  --space-6:  2rem;      /* 32px */
  --space-7:  3rem;      /* 48px */
  --space-8:  4rem;      /* 64px */
  --space-9:  6rem;      /* 96px */
  --space-10: 8rem;      /* 128px */

  /* Section rhythm scales with viewport so a phone is not all padding. */
  --section-y:    clamp(3rem, 6vw, 6rem);
  --section-y-lg: clamp(4rem, 9vw, 9rem);
  --gutter:       clamp(1rem, 4vw, 2rem);
  --content-max:  1200px;
  --content-wide: 1440px;
  --content-card: 34rem;   /* public profile column */
  --content-form: 27rem;   /* auth / narrow settings */

  /* Band rhythm.
     One uniform section padding on every band is what produces long dead
     gaps above and below short content. These four steps let a page
     alternate intensity and quiet: a dense grid gets --band-y-tight, a
     one-idea statement gets --band-y-cinema, and the difference is what
     the eye reads as pacing. Assign per band; never all-cinema. */
  --band-y-tight:  clamp(2.5rem, 4vw, 4rem);
  --band-y:        clamp(3.5rem, 6vw, 6rem);
  --band-y-loose:  clamp(4.5rem, 8vw, 7.5rem);
  --band-y-cinema: clamp(5.5rem, 10vw, 10rem);

  /* Editorial measures.
     A 44rem column inside a 1200px container leaves ~40% of a wide screen
     empty. These are the intentional widths: --measure-lede for a lede
     under a heading, --measure-editorial for a body column that sits
     beside media, --measure-display for a headline that should break
     early on purpose. */
  --measure-display:   22ch;
  --measure-lede:      54ch;
  --measure-editorial: 38rem;

  /* 12-column mental model. Auto-fit grids use --grid-min instead of
     a breakpoint list, so card decks collapse without extra media queries. */
  --grid-cols: 12;
  --grid-min:  16rem;
  --grid-gap:  var(--space-4);

  /* Breakpoints as values for JS and documentation.
     Custom properties cannot be used inside @media in CSS, so sheets still
     write the literal. Keep the literals identical to these tokens. */
  --bp-sm:  480px;   /* auth card padding, compact forms */
  --bp-md:  768px;   /* two-column admin KPIs */
  --bp-lg:  900px;   /* marketing nav, settings sidebar */
  --bp-xl:  960px;   /* dashboard drawer dismisses */
  --bp-2xl: 1200px;  /* --content-max */

  /* ── Geometry ───────────────────────────────────────────────────*/
  --radius-xs:   4px;
  --radius-sm:   6px;
  --radius-md:   10px;
  --radius-lg:   16px;
  --radius-xl:   24px;
  --radius-full: 999px;

  --border-hair: 1px;
  --border-firm: 2px;

  /* ── Depth ──────────────────────────────────────────────────────
     Ink-tinted rather than neutral black: a shadow is light occluded by
     the object above it, so it should carry the scene's colour. */
  --shadow-xs:   0 1px 2px rgba(22, 19, 20, 0.05);
  --shadow-sm:   0 2px 6px rgba(22, 19, 20, 0.07);
  --shadow-md:   0 6px 18px rgba(22, 19, 20, 0.09);
  --shadow-lg:   0 16px 40px rgba(22, 19, 20, 0.13);
  --shadow-xl:   0 28px 70px rgba(22, 19, 20, 0.18);
  --shadow-gold: 0 12px 32px rgba(196, 164, 106, 0.24);
  --shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.55);

  /* ── Motion ─────────────────────────────────────────────────────
     Durations short enough to feel instant, easings weighted so motion
     decelerates into place. --ease-out is the default for anything
     entering; --ease-spring adds a controlled settle for direct input. */
  --dur-instant: 90ms;
  --dur-fast:    160ms;
  --dur-base:    240ms;
  --dur-slow:    380ms;
  --dur-slower:  620ms;
  --dur-cine:    1100ms;

  --ease-out:    cubic-bezier(0.16, 1, 0.3, 1);
  --ease-in-out: cubic-bezier(0.65, 0, 0.35, 1);
  --ease-spring: cubic-bezier(0.34, 1.46, 0.64, 1);
  --ease-linear: linear;

  /* ── Interaction ────────────────────────────────────────────────
     Fitts's law: a target's cost is its distance and size. 44px is the
     smallest comfortable touch target, so it is the floor everywhere
     rather than a mobile-only override. */
  --tap-min:       44px;
  --tap-min-dense: 36px;   /* pointer-only, inside dense admin tables */
  --disabled-opacity: 0.5;

  /* Icon sizes. The default .icon is 1em so it tracks adjacent type;
     these are for standalone marks (nav, empty states, toast). */
  --icon-sm: 16px;
  --icon-md: 20px;
  --icon-lg: 24px;
  --icon-xl: 32px;

  /* One focus ring for the whole product. Gold on any surface, offset so
     it never crowds the glyph it is describing. */
  --focus-ring:   2px solid var(--gold);
  --focus-offset: 3px;

  /* ── Layers ─────────────────────────────────────────────────────
     Named so no component has to invent a z-index. */
  --z-base:    0;
  --z-raised:  10;
  --z-sticky:  100;
  --z-header:  200;
  --z-overlay: 300;
  --z-modal:   400;
  --z-toast:   500;
}

/**
 * Marketing shell lock.
 * Homepage / login / signup must keep Fraunces + Inter even if a later
 * portal sheet remaps :root --display.
 */
body.marketing,
html.pulsar-login,
html.pulsar-signup {
  /* themes/theme1 type + font colors — marketing only */
  --ink:            #07080c;
  --ink-2:          #0d1017;
  --ink-3:          #151923;
  --paper:          #f5f7fb;
  --paper-2:        #eceff5;
  --paper-3:        #e2e7ef;
  --text:           #0c0f15;
  --text-soft:      #5f6878;
  --text-on-dark:   #f7f8fc;
  --text-dark-soft: #9aa5b7;
  --line:           rgba(13, 18, 29, .12);
  --photon:         #6d7cff;
  --photon-2:       #7defff;
  --violet:         #9b78ff;
  --accent:         #6d7cff;
  --accent-soft:    rgba(109, 124, 255, .14);
  --display: "Bodoni 72", Didot, "Iowan Old Style", "Times New Roman", serif;
  --sans:    Inter, ui-sans-serif, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
  --ui:      var(--sans);
  --font-display: var(--display);
  --font-body:    var(--ui);
  --font-ui:      var(--ui);
  --text-primary: var(--text);
  --text-secondary: var(--text-soft);
}

/**
 * Dark surface context.
 *
 * Auth, the cinematic hero and any inverse panel opt in with
 * `data-surface="dark"`. Only the surface roles move — brand hues stay
 * fixed — so a component written once renders correctly in both.
 */
[data-surface="dark"],
html[data-theme="dark"],
html.theme-dark {
  --surface-page:    var(--ink);
  --surface-raised:  var(--ink-90);
  --surface-sunken:  #0B0D13;
  --surface-inverse: var(--paper);
  --surface-line:    rgba(255, 255, 255, 0.12);
  --text-primary:    var(--paper);
  --text-secondary:  #B8B3B4;
  --text-inverse:    var(--ink);
  --bg:              var(--ink);
  --bg-elev:         var(--ink-90);
  --text:            var(--paper);
  --muted:           #B8B3B4;
  --subtle:          #B8B3B4;
  --line:            rgba(255, 255, 255, 0.12);
  --line-strong:     rgba(255, 255, 255, 0.18);
  --surface:         var(--ink-90);

  /* Depth reads as a lift in luminance in the dark, not as a cast shadow. */
  --shadow-xs:    0 1px 2px rgba(0, 0, 0, 0.4);
  --shadow-sm:    0 2px 8px rgba(0, 0, 0, 0.45);
  --shadow-md:    0 8px 24px rgba(0, 0, 0, 0.5);
  --shadow-lg:    0 20px 48px rgba(0, 0, 0, 0.55);
  --shadow-xl:    0 32px 80px rgba(0, 0, 0, 0.6);
  --shadow-inset: inset 0 1px 0 rgba(255, 255, 255, 0.06);
}

html[data-theme="light"],
html.theme-light {
  --bg:              var(--paper);
  --bg-elev:         var(--raised);
  --text:            var(--ink-80);
  --muted:           #5C5758;
  --subtle:          #5C5758;
  --line:            rgba(35, 31, 32, 0.12);
  --line-strong:     rgba(35, 31, 32, 0.18);
  --surface:         var(--raised);
  --surface-page:    var(--paper);
  --surface-raised:  var(--raised);
  --surface-sunken:  #ECECED;
  --surface-line:    rgba(35, 31, 32, 0.12);
  --text-primary:    var(--ink-80);
  --text-secondary:  #5C5758;
  --text-inverse:    var(--paper);
}

/**
 * Reduced motion.
 *
 * Collapse durations to a near-zero value instead of removing transitions,
 * so state changes still resolve to the correct final style. Individual
 * cinematic effects additionally opt out via their own media query.
 */
@media (prefers-reduced-motion: reduce) {
  :root {
    --dur-instant: 1ms;
    --dur-fast:    1ms;
    --dur-base:    1ms;
    --dur-slow:    1ms;
    --dur-slower:  1ms;
    --dur-cine:    1ms;
  }
}

/**
 * Forced-colours (Windows high contrast): let the OS palette win and keep
 * only the geometry, which is what the user asked for.
 */
@media (forced-colors: active) {
  :root {
    --focus-ring: 2px solid CanvasText;
  }
}
