/* ==========================================================================
   UrbanKit Studio — v19 tokens
   Supersedes the v18 palette. Additive on structure, replacing on colour.
   Dark only. No light mode.
   --------------------------------------------------------------------------
   WHY v19 EXISTS
   v18's canvas was #06070A, a neutral near-black at effectively zero chroma.
   That single value, combined with a blue accent, a four-step neutral grey
   ramp, uppercase mono micro-labels, Inter/Archivo/JetBrains Mono, and a
   Tailwind 4px spacing scale, is the recognisable signature of a generated
   design system. v19 keeps the orange, keeps Archivo, and changes the two
   things that carry the tell: the ground gains a hue, and every secondary
   tone becomes a tint of the ground rather than a neutral grey.
   ========================================================================== */

:root {

  /* ------------------------------------------------------------- ground */
  /* Blue-black, not neutral black. Chroma is deliberate and load-bearing:
     it is the difference between a chosen dark and a default dark. */
  --uk-ground:      #070B12;
  --uk-ground-2:    #0C131E;
  --uk-ground-3:    #14202F;
  --uk-ground-4:    #1B2A3C;   /* raised plates, hover fills */

  /* --------------------------------------------------------------- ink */
  /* Warm white against a cool ground. The secondaries are COOL TINTS of
     the ground, so no neutral grey exists anywhere in the system. */
  --uk-ink:         #F2EDE4;   /* 16.90:1 on ground */
  --uk-ink-2:       #A8B6C6;   /*  9.55:1 on ground */
  --uk-ink-3:       #78899C;   /*  5.50:1 on ground — AA floor, see RULES.
                                  This is the ONLY grey in the system, brand
                                  assets included. #8E8779 was a warm leftover
                                  from the buff-ground exploration and fails
                                  rule 6: the ground is blue-black, so its
                                  tints are cool. Retired in 4.2. */

  /* ------------------------------------------------------------ accents */
  --uk-stamp:       #E8744E;   /*  6.61:1 — the recording stamp. unchanged from v18 */
  --uk-stamp-hi:    #FF9169;   /*  8.92:1 — hover, active */
  --uk-blue:        #6BA8DE;   /*  7.77:1 — the one cool accent */
  --uk-blue-dim:    #3E6C96;   /*  non-text fills only; hosts ink at 4.75:1 */

  /* Logo only. NOT a UI token, and not interchangeable with --uk-blue.
     The mark appears on both the site and white browser chrome, and no
     single hex clears 4.5:1 against both: the ceiling is 4.44:1 each way.
     This value sits at that ceiling (4.47 on ground, 4.41 on white).
     --uk-blue is 2.54:1 on white and must never be used in the mark. */
  --uk-brand-blue:  #3A76D8;

  /* --------------------------------------------------------------- rule */
  --uk-rule:        rgba(168, 182, 198, .18);
  --uk-rule-hi:     rgba(168, 182, 198, .34);

  /* ------------------------------------------------------------- sphere */
  /* Globe body. Land is warm, water is cool, and both are shaded per-cell
     by real solar altitude at render time — these are the endpoints of that
     interpolation, not flat fills. */
  --uk-ocean-day:   #101E2F;
  --uk-ocean-night: #0B1420;
  --uk-land-day:    #C9A88A;
  --uk-land-night:  #3B4656;
  --uk-limb:        #2A3849;
  --uk-grat:        rgba(168, 182, 198, .10);
  --uk-grat-key:    rgba(232, 116, 78, .22);   /* prime meridian, equator */
  --uk-terminator:  rgba(232, 116, 78, .55);
  --uk-subsolar:    #FFC08A;
  --uk-sublunar:    rgba(210, 222, 236, .90);

  /* ------------------------------------------------------------- status */
  /* Colour is the SECOND channel. Shape is the first. See SHAPE CONTRACT.
     Off the globe there is a FOURTH channel: not found inverts. */
  --uk-found:       var(--uk-blue);    /* ok       · disc + centre dot */
  --uk-calculated:  var(--uk-stamp);   /* degraded · crosshair through disc */
  --uk-notfound:    var(--uk-ink);     /* down     · X in circle, inverted chip */
  --uk-unknown:     var(--uk-ink-3);   /* never reported · HOLLOW SQUARE.
                                          The other three are round because
                                          they are all observations. This one
                                          is not an observation. */

  /* --------------------------------------------------------------- type */
  /* Three families. Archivo carries over from v18 but is pushed onto its
     WIDTH axis, which v18 never used — expanded display is where the
     futuristic register comes from, not from a new sci-fi face. */
  --uk-display: "Archivo", "Archivo Expanded", system-ui, sans-serif;
  --uk-text:    "Satoshi", system-ui, -apple-system, sans-serif;
  --uk-data:    "IBM Plex Mono", ui-monospace, monospace;

  /* Scale. The v18 problem was that everything sat between 13 and 46px, so
     nothing had presence. Real jumps, roughly 1.4x, with a deliberate gap
     between display-l and title. */
  --uk-t-hero:      clamp(56px, 6.4vw, 104px);
  --uk-t-display:   clamp(38px, 3.8vw, 58px);
  --uk-t-title:     28px;
  --uk-t-subtitle:  20px;
  --uk-t-lead:      18px;
  --uk-t-body:      16px;
  --uk-t-small:     14px;

  /* Data type is its own ramp. Figures are the subject, so they get sizes
     the prose scale does not have. */
  --uk-t-figure-xl: 44px;
  --uk-t-figure:    30px;
  --uk-t-data:      13px;
  --uk-t-label:     10.5px;

  --uk-lh-tight:    1.02;   /* hero, display */
  --uk-lh-title:    1.12;
  --uk-lh-body:     1.62;
  --uk-lh-data:     1.45;

  --uk-tr-hero:     -.032em;
  --uk-tr-display:  -.026em;
  --uk-tr-title:    -.018em;
  --uk-tr-body:      0;
  --uk-tr-figure:   -.035em;
  --uk-tr-label:     .09em;

  --uk-wd-display:  125;    /* Archivo width axis, expanded */
  --uk-wd-body:     100;

  /* ------------------------------------------------------------- motion */
  --uk-ease:        cubic-bezier(.22, .61, .36, 1);   /* weighted settle */
  --uk-ease-signal: cubic-bezier(.16, 1, .30, 1);     /* attack, long decay */
  --uk-t-fast:      140ms;
  --uk-t-base:      220ms;
  --uk-t-slow:      420ms;
  --uk-pulse-calc:  2400ms;   /* degraded, calm */
  --uk-pulse-nf:    1200ms;   /* down, twice the rate */
  --uk-globe-ease:  .14;      /* per-frame lerp toward camera target */

  /* ------------------------------------------------------------ spacing */
  /* 5px base, off the 4px grid on purpose. The 4/8/12/16/24/32/48/64/96
     ramp is Tailwind's verbatim and is one of the nine tells. */
  --uk-s-1: 5px;  --uk-s-2: 10px; --uk-s-3: 15px; --uk-s-4: 20px;
  --uk-s-5: 26px; --uk-s-6: 34px; --uk-s-7: 44px; --uk-s-8: 60px;
  --uk-s-9: 80px; --uk-s-10: 110px;

  --uk-r: 0;   /* zero radius everywhere, no exceptions */
}

/* ==========================================================================
   TYPE ROLES
   ========================================================================== */

.uk-hero     { font-family: var(--uk-display); font-variation-settings: "wdth" var(--uk-wd-display); font-weight: 700; font-size: var(--uk-t-hero); line-height: var(--uk-lh-tight); letter-spacing: var(--uk-tr-hero); color: var(--uk-ink); }
.uk-display  { font-family: var(--uk-display); font-variation-settings: "wdth" var(--uk-wd-display); font-weight: 700; font-size: var(--uk-t-display); line-height: var(--uk-lh-tight); letter-spacing: var(--uk-tr-display); color: var(--uk-ink); }
.uk-title    { font-family: var(--uk-display); font-variation-settings: "wdth" 112; font-weight: 700; font-size: var(--uk-t-title); line-height: var(--uk-lh-title); letter-spacing: var(--uk-tr-title); color: var(--uk-ink); }
.uk-subtitle { font-family: var(--uk-text); font-weight: 500; font-size: var(--uk-t-subtitle); line-height: 1.35; color: var(--uk-ink); }
.uk-lead     { font-family: var(--uk-text); font-weight: 400; font-size: var(--uk-t-lead); line-height: 1.55; color: var(--uk-ink-2); }
.uk-body     { font-family: var(--uk-text); font-weight: 400; font-size: var(--uk-t-body); line-height: var(--uk-lh-body); color: var(--uk-ink-2); }
.uk-small    { font-family: var(--uk-text); font-weight: 400; font-size: var(--uk-t-small); line-height: 1.5; color: var(--uk-ink-2); }

.uk-figure-xl{ font-family: var(--uk-data); font-weight: 400; font-size: var(--uk-t-figure-xl); line-height: 1; letter-spacing: var(--uk-tr-figure); font-variant-numeric: tabular-nums; color: var(--uk-ink); }
.uk-figure   { font-family: var(--uk-data); font-weight: 400; font-size: var(--uk-t-figure); line-height: 1; letter-spacing: var(--uk-tr-figure); font-variant-numeric: tabular-nums; color: var(--uk-ink); }
.uk-data     { font-family: var(--uk-data); font-weight: 400; font-size: var(--uk-t-data); line-height: var(--uk-lh-data); letter-spacing: -.01em; font-variant-numeric: tabular-nums; color: var(--uk-ink); }
.uk-label    { font-family: var(--uk-data); font-weight: 400; font-size: var(--uk-t-label); line-height: 1.3; letter-spacing: var(--uk-tr-label); text-transform: uppercase; color: var(--uk-ink-3); }

/* ==========================================================================
   SHAPE CONTRACT  (WCAG 1.4.1)

   State is carried by SILHOUETTE first, colour second, rate third:

     found       disc + centre dot         static
     calculated  crosshair through disc    2400ms pulse
     not found   X inside circle           1200ms pulse

   Disc / crosshair / X are distinguishable in greyscale, at 4px, and under
   every colour vision deficiency. A red-green pair is not: v18's #16A55E
   and #E84545 sat at relative luminance 0.279 and 0.218, a mutual contrast
   of 1.23:1, so both hue and lightness failed under deuteranopia. That is
   why v19 assigns blue to found and reserves red-orange for calculated.
   ========================================================================== */

@keyframes uk-pulse { 0%,100% { opacity: .55 } 50% { opacity: 1 } }
.uk-mark--calculated { animation: uk-pulse var(--uk-pulse-calc) var(--uk-ease-signal) infinite; }
.uk-mark--notfound   { animation: uk-pulse var(--uk-pulse-nf)   var(--uk-ease-signal) infinite; }

@media (prefers-reduced-motion: reduce) {
  .uk-mark--calculated, .uk-mark--notfound { animation: none; opacity: .85; }
}

/* Off the globe, colour alone is not enough either: --uk-notfound is warm
   white, so a status word set in it reads as ordinary text rather than an
   alarm. Not found is therefore the ONLY state that inverts, anywhere in the
   system. Found and calculated never invert; a healthy row stays quiet. */
.uk-state--notfound { background: var(--uk-ink); color: var(--uk-ground); padding: 3px 8px 4px; font-weight: 500; }

/* ==========================================================================
   BUTTON CONTRACT  (round 4.3)

   Contrast is a property of a PAIR. These are the only sanctioned pairings.
   Primary is the stamp, not the blue: ink on --uk-blue-dim is 4.75:1 and
   blue-dim is a non-text fill, while stamp carrying ground text is 6.61:1.

   THERE IS NO ANCHOR RESET. Two attempts at one both failed the same way:
   `color: inherit` at (0,1,1) outranked .uk-btn--primary at (0,1,0), and
   `border-bottom: 0` — a shorthand, so width AND style AND colour — did the
   same to every variant's border. A third attempt would fail too, because a
   reset can only say `inherit` or a literal, and neither is "whatever the
   variant says".

   So the prose link rule is scoped to exclude buttons instead. a:not(.uk-btn)
   simply does not match a button, which means there is nothing to outrank and
   nothing to keep in sync. Any future component that is an anchor joins the
   :not() list. See the prose link block at the foot of this file.
   ========================================================================== */

.uk-btn { display: inline-block; padding: 12px 20px; border-radius: var(--uk-r); border: 1px solid transparent; font-family: var(--uk-data); font-size: 12px; font-weight: 500; letter-spacing: .08em; text-transform: uppercase; cursor: pointer; text-decoration: none; transition: background var(--uk-t-fast) var(--uk-ease), border-color var(--uk-t-fast) var(--uk-ease), color var(--uk-t-fast) var(--uk-ease); }

/* Base colour, so a variant-less .uk-btn cannot fall through to anything.
   Variants tie at (0,1,0)/(0,2,0) and win on source order, so THE VARIANT
   BLOCK MUST STAY BELOW THIS LINE. */
.uk-btn { color: var(--uk-ink); }
.uk-btn:hover { color: var(--uk-ink); }

/* Every variant states border-color on EVERY interactive state, including
   the ones where it does not change. A state that leaves border-color unset
   is the hole a reset used to paper over. */
.uk-btn--primary { background: var(--uk-stamp); border-color: var(--uk-stamp); color: var(--uk-ground); }          /* 6.61:1 */
.uk-btn--primary:hover { background: var(--uk-stamp-hi); border-color: var(--uk-stamp-hi); color: var(--uk-ground); } /* 8.92:1 */
.uk-btn--primary:focus-visible { border-color: var(--uk-stamp); }
.uk-btn--primary:disabled, .uk-btn--primary[aria-disabled="true"] { background: var(--uk-ground-2); border-color: var(--uk-rule); color: var(--uk-ink-3); cursor: not-allowed; } /* 5.19:1 */

.uk-btn--secondary { background: transparent; border-color: var(--uk-rule-hi); color: var(--uk-ink); }             /* 16.90:1 */
.uk-btn--secondary:hover { background: var(--uk-ground-3); border-color: var(--uk-ink-3); color: var(--uk-ink); } /* 14.10:1 */
.uk-btn--secondary:focus-visible { border-color: var(--uk-rule-hi); }
.uk-btn--secondary:disabled, .uk-btn--secondary[aria-disabled="true"] { border-color: var(--uk-rule); color: var(--uk-ink-3); cursor: not-allowed; } /* 5.50:1 */

.uk-btn--ghost { background: transparent; border-color: transparent; color: var(--uk-ink-2); padding-left: 0; padding-right: 0; } /* 9.55:1 */
.uk-btn--ghost:hover { border-color: transparent; color: var(--uk-ink); box-shadow: inset 0 -1px 0 var(--uk-ink); } /* 16.90:1 */
.uk-btn--ghost:focus-visible { border-color: transparent; }
.uk-btn--ghost:disabled, .uk-btn--ghost[aria-disabled="true"] { border-color: transparent; color: var(--uk-ink-3); box-shadow: none; cursor: not-allowed; }

.uk-btn--ghost { background: transparent; border-color: transparent; color: var(--uk-ink-2); padding-left: 0; padding-right: 0; } /* 9.55:1 */
.uk-btn--ghost:hover { color: var(--uk-ink); box-shadow: inset 0 -1px 0 var(--uk-ink); }                          /* 16.90:1 */
.uk-btn--ghost:disabled, .uk-btn--ghost[aria-disabled="true"] { color: var(--uk-ink-3); box-shadow: none; cursor: not-allowed; }

/* ONE focus treatment for every interactive element, whatever its fill.
   The 3px offset puts the ring in the page ground at 16.90:1, so it never
   depends on what the control is filled with. A blue ring on a stamp fill
   would be 1.18:1: the same failure relocated. */
.uk-btn:focus-visible { outline: 2px solid var(--uk-ink); outline-offset: 3px; }

/* ==========================================================================
   RULES THAT ARE NOT SUGGESTIONS

   1. --uk-ink-3 is the metadata FLOOR at 5.50:1 on --uk-ground. Nothing
      dimmer ships as text. This is the rule most often broken in review —
      HUD labels, hint text and axis ticks all drift below it. If a label
      looks too loud, make it smaller, not dimmer.

      It is also NOT a text colour on --uk-ground-4, where it measures
      4.01:1 and fails. On --uk-ground-3 it is 4.53:1, which passes with no
      margin, so prefer --uk-ink-2 there below 14px.

   2. Nothing renders as text directly on lit halftone. --uk-ink on
      --uk-land-day is 1.92:1 and fails outright. Every overlay label sits
      on a plate at rgba(7,11,18,.88) or darker.

   3. --uk-blue-dim and --uk-land-* are non-text values. Never a label.

   4. Radius is 0 everywhere. There is no exception, including buttons,
      inputs, plates, and the like-counter-style pill patterns from v18.

   5. Spacing comes off the 5px scale. Do not reintroduce a 4px ramp.

   6. Every colour is a tint of the ground, the stamp, or the blue. If a
      value has near-zero chroma and is not pure ink, it does not belong.
      This binds brand assets too. A brand-only value must earn itself with
      a constraint the UI does not face — --uk-brand-blue does, because it
      must clear white browser chrome. A grey on the ground does not.

   7. Nothing is drawn that does not carry a measurement. On the globe that
      means: no arcs between points that do not exchange data, no glow that
      is not a state, no grid that is not a real graticule.

   8. HOVER NEVER CHANGES HUE. A hover may change lightness within one hue,
      change a fill, or add a rule. It may never substitute one accent for
      another: the new hue has no defined relationship to the surface it
      lands on. This is what produced a 1.18:1 primary CTA in review.

   9. --uk-stamp and --uk-stamp-hi are FILL colours, or text on a ground
      tint. Never text on a chromatic fill. Stamp on --uk-blue is 1.18:1,
      on --uk-blue-dim 1.86:1, on --uk-land-day 1.34:1. The useful form is
      the inverse: stamp as a fill carrying --uk-ground text at 6.61:1.

  10. THE PROSE LINK RULE DOES NOT MATCH COMPONENTS. Not "is outranked by" —
      does not match. Scope it with :not() and add every anchor-based
      component to the list. A reset can only say `inherit` or a literal,
      and neither of those means "whatever the variant says", so a reset
      can never be correct here. Two attempts proved it: `color: inherit`
      took the page ink onto an orange fill at 2.56:1, and `border-bottom: 0`
      — a shorthand touching width, style and colour — left every anchor
      button an open-bottomed rectangle.

      Corollary: every variant states border-color on every interactive
      state, including states where it does not change. An unset property
      is what a reset papers over.

  11. Focus is one treatment everywhere: 2px --uk-ink outline, 3px offset.
      A 0-blur ring is a border and is permitted. A blurred one is a glow
      and is not.

  12. THE CASING RULE. Every status mark on a surface whose lightness varies
      is drawn twice: a 4.5px casing, then the mark on top. The casing is
      --uk-ground where the surface luminance beneath the mark exceeds
      Y 0.169, and --uk-ink where it does not. That threshold is where the
      two polarities give identical contrast, 4.11:1, which is therefore the
      floor anywhere on the globe at any hour.

  13. unknown is not a degree of health. It does not pulse, does not
      aggregate into a health percentage, and is never counted as found.
      Report it separately: "147 of 151 responding, 2 not yet reported".

  14. Aggregates inherit SILHOUETTE, not just colour. Any element summarising
      several statuses carries the silhouette of the worst status inside it.
      Worst is ordered: not found, calculated, unknown, found.
   ========================================================================== */

/* Prose links. Scoped so a button never matches: :not(.uk-btn) contributes
   (0,1,0), giving (0,1,1) here and (0,2,1) on hover, but a .uk-btn does not
   match these selectors AT ALL, so specificity never has to be reasoned
   about. Add any future anchor-based component to the :not() list. */
a:not(.uk-btn) { color: var(--uk-blue); text-decoration: none; border-bottom: 1px solid rgba(107,168,222,.36); transition: color var(--uk-t-fast) var(--uk-ease), border-color var(--uk-t-fast) var(--uk-ease); }
a:not(.uk-btn):hover { color: var(--uk-stamp); border-bottom-color: var(--uk-stamp); }
a:not(.uk-btn):focus-visible { outline: 1px solid var(--uk-blue); outline-offset: 3px; border-bottom-color: transparent; }

.uk-plate { background: rgba(7,11,18,.88); border: 1px solid var(--uk-rule); backdrop-filter: blur(3px); border-radius: var(--uk-r); }
::selection { background: rgba(232,116,78,.32); color: var(--uk-ink); }
