/* ==========================================================================
   CyberFeed

   EVERY design token lives in :root below and nowhere else in the codebase.
   No hex value appears anywhere outside this block — not in app.js, not in
   the Scriptable widget (which redefines the same tokens once at its top).
   Changing the palette means editing one block.

   Colours are stored as RGB *channels* rather than hex so that tints derive
   from a single definition: rgb(var(--powder-rgb) / 0.2). Defining the tint
   separately would mean the same colour written twice, which is exactly the
   thing that rots.
   ========================================================================== */

:root {
  /* --- palette: cool, dusky, low saturation ---------------------------- */
  /* Analogous blues carry the interface; one warm split-complement is held
     in reserve for danger and never spent on decoration. */
  --ink-rgb:      22 35 58;      /* deep navy — all primary text          */
  --ink-soft-rgb: 91 107 130;    /* slate — timestamps, sources, secondary */
  --paper-rgb:    237 240 242;   /* cool off-white ground                  */
  --surface-rgb:  248 250 251;   /* raised — separated by value, not border*/
  --powder-rgb:   169 199 222;   /* baby blue — interactive + threat tags  */
  --sage-rgb:     127 168 148;   /* muted green — domain tags              */
  --signal-rgb:   192 138 74;    /* warm ochre — KEV and critical CVEs ONLY*/

  --ink:      rgb(var(--ink-rgb));
  --ink-soft: rgb(var(--ink-soft-rgb));
  --paper:    rgb(var(--paper-rgb));
  --surface:  rgb(var(--surface-rgb));
  --powder:   rgb(var(--powder-rgb));
  --sage:     rgb(var(--sage-rgb));
  --signal:   rgb(var(--signal-rgb));

  /* Text that sits on top of a filled --signal ground. Ochre is light enough
     that dark ink is the only accessible choice: light text on #C08A4A is
     1.8:1 and fails outright, while ink on it measures 5.2:1 in light mode
     and 7.6:1 in dark. The banner therefore reads identically in both
     themes, which is also right — an alert should not soften at night. */
  --on-signal: rgb(var(--ink-rgb));

  /* Tint strengths. Chips carry their family tint always; selection is a
     step in value, not a border appearing. */
  --tint-rest:     0.15;
  --tint-selected: 0.25;

  --hairline: rgb(var(--ink-soft-rgb) / 0.15);

  /* --- type ------------------------------------------------------------ */
  /* Newsreader: a serif drawn for reading news on screens. Its job is
     atmosphere and reading comfort, not decoration.
     Plex Sans: all UI.  Plex Mono: CVE identifiers only, because a CVE ID is
     a string you compare character by character and tabular figures help. */
  --font-serif: 'Newsreader', Georgia, 'Times New Roman', serif;
  --font-sans:  'IBM Plex Sans', -apple-system, BlinkMacSystemFont, 'Segoe UI', sans-serif;
  --font-mono:  'IBM Plex Mono', ui-monospace, SFMono-Regular, Menlo, monospace;

  /* Major third (1.25) from a 17px base: 17 / 21 / 27 / 33.
     --size-ui sits one step *below* the base for metadata and chips. */
  --size-ui:    13.6px;
  --size-base:  17px;
  --size-title: 21px;
  --size-hero:  27px;
  --size-max:   33px;

  --leading-body:  1.6;
  --leading-tight: 1.25;

  /* --- space ----------------------------------------------------------- */
  --gutter:    20px;   /* side margins, per the layout brief */
  --space-xs:  4px;
  --space-sm:  8px;
  --space-md:  14px;
  --space-lg:  22px;
  --space-xl:  34px;

  --radius: 8px;       /* ONLY the daily term and the KEV banner get this */
  --tap:    44px;      /* minimum touch target */

  --nav-height: 58px;

  color-scheme: light;
}

/* Dark mode inverts the value structure without changing the hues.
   Declared twice on purpose: once for the system preference (guarded so an
   explicit light choice still wins) and once for the explicit toggle, so the
   header control works in both directions. */
@media (prefers-color-scheme: dark) {
  :root:not([data-theme='light']) {
    --ink-rgb:      227 233 239;
    --ink-soft-rgb: 138 155 176;
    --paper-rgb:    19 30 46;
    --surface-rgb:  27 41 57;
    --signal-rgb:   216 165 102;   /* lifted to hold contrast on dark ground */
    --on-signal:    rgb(19 30 46);
    color-scheme: dark;
  }
}

:root[data-theme='dark'] {
  --ink-rgb:      227 233 239;
  --ink-soft-rgb: 138 155 176;
  --paper-rgb:    19 30 46;
  --surface-rgb:  27 41 57;
  --signal-rgb:   216 165 102;
  --on-signal:    rgb(19 30 46);
  color-scheme: dark;
}

/* ==========================================================================
   base
   ========================================================================== */

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

html { -webkit-text-size-adjust: 100%; }

body {
  margin: 0;
  background: var(--paper);
  color: var(--ink);
  font-family: var(--font-sans);
  font-size: var(--size-base);
  line-height: var(--leading-body);
  /* room for the fixed bottom nav plus the iPhone home indicator */
  padding-bottom: calc(var(--nav-height) + env(safe-area-inset-bottom, 0px));
  -webkit-font-smoothing: antialiased;
}

a { color: inherit; }

button {
  font: inherit;
  color: inherit;
  background: none;
  border: 0;
  cursor: pointer;
}

/* Visible focus for keyboard users, suppressed for pointer input. */
:focus-visible {
  outline: 2px solid var(--powder);
  outline-offset: 3px;
  border-radius: 2px;
}

.visually-hidden {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0;
  overflow: hidden;
  clip-path: inset(50%);
  white-space: nowrap;
}

/* ==========================================================================
   header — quiet
   ========================================================================== */

.masthead {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: var(--space-md);
  padding: var(--space-md) var(--gutter) var(--space-sm);
}

.masthead__title {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--size-title);
  font-weight: 500;
  letter-spacing: -0.01em;
  line-height: var(--leading-tight);
}

.theme-toggle {
  display: grid;
  place-items: center;
  width: var(--tap);
  height: var(--tap);
  margin-right: calc(var(--space-sm) * -1);
  color: var(--ink-soft);
  font-size: var(--size-base);
  border-radius: 50%;
}

.theme-toggle:hover { color: var(--ink); }

/* ==========================================================================
   KEV banner — the one place --signal is spent
   ========================================================================== */

.kev {
  margin: var(--space-sm) var(--gutter) var(--space-lg);
  padding: var(--space-md) var(--space-lg);
  background: var(--signal);
  color: var(--on-signal);
  border-radius: var(--radius);
}

.kev__label {
  margin: 0 0 var(--space-xs);
  font-size: var(--size-ui);
  font-weight: 600;
}

.kev__body { margin: 0; font-size: var(--size-base); line-height: 1.45; }

.kev__list {
  margin: var(--space-sm) 0 0;
  padding: 0;
  list-style: none;
  font-family: var(--font-mono);
  font-size: var(--size-ui);
}

/* ==========================================================================
   daily term — the hero, and the only loud thing on a normal day
   ========================================================================== */

.term {
  margin: var(--space-sm) var(--gutter) var(--space-lg);
  padding: var(--space-lg);
  background: rgb(var(--powder-rgb) / var(--tint-rest));
  border-radius: var(--radius);
}

.term__label {
  margin: 0 0 var(--space-sm);
  color: var(--ink-soft);
  font-size: var(--size-ui);
  /* Sentence case, no tracking. An all-caps tracked-out label here is the
     single most common tell in this genre. */
}

.term__name {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--size-hero);
  font-weight: 500;
  line-height: var(--leading-tight);
  letter-spacing: -0.015em;
}

.term__short {
  margin: var(--space-sm) 0 0;
  font-family: var(--font-serif);
  font-size: var(--size-base);
  line-height: var(--leading-body);
  max-width: 62ch;
}

.term__more {
  margin-top: var(--space-md);
  padding: 0;
  color: var(--ink-soft);
  font-size: var(--size-ui);
  min-height: var(--tap);
  display: flex;
  align-items: center;
  gap: var(--space-xs);
}

.term__more:hover { color: var(--ink); }

.term__detail { display: grid; grid-template-rows: 0fr; transition: grid-template-rows 260ms ease; }
.term[data-open='true'] .term__detail { grid-template-rows: 1fr; }
.term__detail > div { overflow: hidden; }

.term__detail p {
  margin: 0 0 var(--space-md);
  font-family: var(--font-serif);
  line-height: var(--leading-body);
  max-width: 62ch;
}

.term__lab {
  margin: 0;
  padding: var(--space-md);
  background: rgb(var(--surface-rgb) / 0.7);
  border-radius: var(--radius);
  font-family: var(--font-mono);
  font-size: var(--size-ui);
  line-height: 1.5;
  overflow-x: auto;
}

.term__lab strong {
  display: block;
  margin-bottom: var(--space-xs);
  font-family: var(--font-sans);
  font-weight: 600;
  color: var(--ink-soft);
}

/* The one orchestrated moment: the term settles on the first open of a day. */
@media (prefers-reduced-motion: no-preference) {
  .term[data-fresh='true'] { animation: settle 620ms cubic-bezier(0.22, 1, 0.36, 1) both; }
  @keyframes settle {
    from { opacity: 0; transform: translateY(8px); }
    to   { opacity: 1; transform: none; }
  }
}

/* ==========================================================================
   filter chips — directly under the content they filter, never in a menu
   ========================================================================== */

.filters {
  display: flex;
  gap: var(--space-sm);
  padding: var(--space-xs) var(--gutter) var(--space-md);
  overflow-x: auto;
  scrollbar-width: none;
  -webkit-overflow-scrolling: touch;
}

.filters::-webkit-scrollbar { display: none; }

.chip {
  flex: 0 0 auto;
  padding: var(--space-sm) var(--space-md);
  border-radius: 999px;
  font-size: var(--size-ui);
  color: var(--ink-soft);
  white-space: nowrap;
  /* Chips always carry their family tint. Selection raises the value; it
     does not add a border, which is the usual and duller answer. */
  background: rgb(var(--chip-rgb) / var(--tint-rest));
  transition: background-color 140ms ease, color 140ms ease;
}

.chip[aria-pressed='true'] {
  background: rgb(var(--chip-rgb) / var(--tint-selected));
  color: var(--ink);
  font-weight: 500;
}

/* The two tag families are semantic, not decorative: powder marks what
   happened, sage marks the subject area. Adding a tag means adding its name
   to one of these two selectors. */
.chip { --chip-rgb: var(--sage-rgb); }

.chip[data-tag='vuln'],
.chip[data-tag='ransomware'],
.chip[data-tag='malware'],
.chip[data-tag='incident'] { --chip-rgb: var(--powder-rgb); }

.chip[data-tag='misc'] { --chip-rgb: var(--ink-soft-rgb); }

/* ==========================================================================
   article list — not cards. Text, space, and a hairline.
   ========================================================================== */

.feed { padding: 0 var(--gutter); }

.story {
  display: block;
  padding: var(--space-lg) 0;
  border-top: 1px solid var(--hairline);
  text-decoration: none;
}

.feed .story:first-child { border-top: 0; padding-top: var(--space-xs); }

.story__title {
  margin: 0;
  font-family: var(--font-serif);
  font-size: var(--size-title);
  font-weight: 450;
  line-height: 1.32;
  letter-spacing: -0.01em;
  max-width: 34ch;
  /* Line length under 70 characters at body size; titles run shorter still. */
}

.story__meta {
  margin-top: var(--space-sm);
  color: var(--ink-soft);
  font-size: var(--size-ui);
}

/* Cross-source links are the visible payoff of the dedupe work, so they get
   their own line rather than being appended to the metadata as another
   dot-separated fragment. */
.story__also {
  margin-top: var(--space-xs);
  color: var(--ink-soft);
  font-size: var(--size-ui);
}

.story__also a { text-decoration: underline; text-underline-offset: 2px; }

/* Read articles dim. Opacity rather than a colour change so it degrades
   correctly in both themes. */
.story[data-read='true'] .story__title { opacity: 0.55; }

.story__cve {
  font-family: var(--font-mono);
  font-size: 0.94em;
}

/* ==========================================================================
   empty + status states
   ========================================================================== */

.notice {
  padding: var(--space-xl) var(--gutter);
  color: var(--ink-soft);
  max-width: 46ch;
}

.notice p { margin: 0 0 var(--space-sm); }
.notice strong { color: var(--ink); font-weight: 500; }

.updated {
  padding: var(--space-lg) var(--gutter) var(--space-xl);
  color: var(--ink-soft);
  font-size: var(--size-ui);
}

/* ==========================================================================
   bottom navigation — four destinations, always visible, thumb reach
   ========================================================================== */

.nav {
  position: fixed;
  inset: auto 0 0;
  display: flex;
  background: rgb(var(--surface-rgb) / 0.94);
  backdrop-filter: blur(12px);
  border-top: 1px solid var(--hairline);
  padding-bottom: env(safe-area-inset-bottom, 0px);
}

.nav__item {
  flex: 1;
  min-height: var(--nav-height);
  display: grid;
  place-items: center;
  /* Text labels rather than icons. Four generic glyphs — house, bookmark,
     calendar, speaker — would look identical on any app in any category. */
  font-size: var(--size-ui);
  color: var(--ink-soft);
  text-decoration: none;
}

.nav__item[aria-current='page'] { color: var(--ink); font-weight: 500; }

/* ==========================================================================
   wider screens — the layout is phone-first; just stop the line getting long
   ========================================================================== */

@media (min-width: 620px) {
  .app { max-width: 640px; margin: 0 auto; }
  .story__title { max-width: 42ch; }
}
