/* ============================================================================
   Job AI Crawler — design system

   The app is a readout: nearly everything on screen is a count, a share, a
   match or a mark. So the system has three voices — display (Bricolage) for
   the page's subject, body (Public Sans) for prose, and mono (IBM Plex Mono)
   for every piece of data. If it's a number, a URL or a date, it's mono.

   The palette grows out of the pine that already marked "open" postings.
   Pine means live/affirmative, oxblood means closed/destructive, amber means
   "look twice". Nothing else is coloured.
   ========================================================================= */

:root {
  /* Ground and surfaces */
  --bone: #e2e3dc;          /* page ground — pressed board */
  --surface: #fbfbf8;       /* cards, tables, panels */
  --surface-sunk: #ecece5;  /* wells: code blocks, staging area */

  /* Ink */
  --ink: #14201d;
  --ink-muted: #5e6864;
  --ink-faint: #8b938f;

  /* Rules */
  --rule: #cdcfc6;
  --rule-strong: #b3b6ab;

  /* Signal */
  --pine: #0e4f45;
  --pine-deep: #093630;
  --pine-tint: #d7e5e1;
  --pine-wash: #eef4f2;

  --oxblood: #8c2f39;
  --oxblood-tint: #f5e3e4;
  --amber: #9a6410;
  --amber-tint: #f7ecd8;

  /* The profile board's three columns, and so every skill the app draws.
     Pine says you have it; pine at half strength says you have some of it,
     which is the one place a lighter shade of the same colour states the
     relationship exactly; rust says you would like to learn it, and is a
     different colour because wanting is not a weaker kind of having.

     The two they replaced were amber and gold — 12° of hue apart, and 1.06:1
     against each other, which is to say one colour with extra steps. Amber
     is still amber elsewhere ("look twice"); it simply no longer answers for
     a column. */
  --pine-soft: #2f7a6b;
  --rust: #a8500c;

  /* Each of the three at a tenth, for the ground under something rather
     than the something itself — a skill on the CV, a column on the board.
     Held here because those two have to agree on it. */
  --pine-10: rgba(14, 79, 69, 0.1);
  --pine-soft-10: rgba(47, 122, 107, 0.1);
  --rust-10: rgba(168, 80, 12, 0.1);

  /* Type */
  --font-display: "Bricolage Grotesque", "Public Sans", system-ui, sans-serif;
  --font-body: "Public Sans", system-ui, -apple-system, sans-serif;
  --font-mono: "IBM Plex Mono", ui-monospace, "SF Mono", Menlo, monospace;

  /* Space */
  --gap: 1rem;
  --pad: 1.25rem;
  --rail: 232px;

  --radius: 3px;   /* restrained on purpose: this is an instrument, not a toy */
  --shadow: 0 1px 2px rgba(20, 32, 29, 0.06), 0 8px 24px rgba(20, 32, 29, 0.06);
}

/* ── Reset ──────────────────────────────────────────────────────────────── */

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

body {
  margin: 0;
  background: var(--bone);
  color: var(--ink);
  font-family: var(--font-body);
  font-size: 0.9375rem;
  line-height: 1.55;
  -webkit-font-smoothing: antialiased;
}

/* While an in-place navigation is in flight (see nav.js). */
html.is-navigating { cursor: progress; }

h1, h2, h3, h4 { margin: 0; font-weight: 600; line-height: 1.2; }
p { margin: 0 0 0.75rem; }
p:last-child { margin-bottom: 0; }

a { color: var(--pine); text-decoration-color: var(--pine-tint); text-underline-offset: 2px; }
a:hover { text-decoration-color: currentColor; }

:focus-visible {
  outline: 2px solid var(--pine);
  outline-offset: 2px;
  border-radius: 2px;
}

/* ── Shell ──────────────────────────────────────────────────────────────── */

.shell { display: flex; min-height: 100vh; align-items: flex-start; }

.rail {
  position: sticky;
  top: 0;
  flex: 0 0 var(--rail);
  width: var(--rail);
  height: 100vh;
  padding: 1.5rem 1rem;
  background: var(--pine-deep);
  color: #dfe8e5;
  display: flex;
  flex-direction: column;
  gap: 1.5rem;
}

.rail__mark {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 1.0625rem;
  letter-spacing: -0.025em;
  line-height: 1.15;
  color: #fff;
  text-decoration: none;
  padding: 0 0.5rem;
  display: block;
}
.rail__mark span { display: block; color: #7fada4; }

.rail__nav { display: flex; flex-direction: column; gap: 1px; }

.rail__link {
  display: block;
  padding: 0.45rem 0.5rem;
  border-radius: var(--radius);
  color: #b9cdc8;
  text-decoration: none;
  font-size: 0.875rem;
  font-weight: 500;
}
.rail__link:hover { background: rgba(255, 255, 255, 0.06); color: #fff; }

.rail__link[aria-current="page"] {
  background: rgba(255, 255, 255, 0.1);
  color: #fff;
  font-weight: 600;
}

.rail__foot {
  margin-top: auto;
  padding: 0 0.5rem;
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  line-height: 1.5;
  color: #56847b;
}

.main { flex: 1; min-width: 0; padding: 2rem 2.25rem 4rem; }
.main__inner { max-width: 1180px; }

/* ── Page head ──────────────────────────────────────────────────────────── */

.page-head { margin-bottom: 1.75rem; }

.eyebrow {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.09em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 0.4rem;
}

.page-title {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: clamp(1.75rem, 3.2vw, 2.375rem);
  letter-spacing: -0.03em;
  line-height: 1.05;
}

.page-lede {
  margin-top: 0.6rem;
  max-width: 62ch;
  color: var(--ink-muted);
}

.section-title {
  font-family: var(--font-display);
  font-weight: 700;
  font-size: 1.125rem;
  letter-spacing: -0.02em;
  margin-bottom: 0.5rem;
}

.hint { color: var(--ink-muted); font-size: 0.8125rem; max-width: 68ch; }

/* ── Panel ──────────────────────────────────────────────────────────────── */

.panel {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: var(--pad);
}
/* Spacing between panels is the stack's job, never the panel's: a bare
   `.panel + .panel` rule also fires on grid siblings, which pushes every
   column but the first down by a rem and un-aligns the row. */
.stack > * + * { margin-top: 1.25rem; }
.stack-lg > * + * { margin-top: 2.25rem; }

.well {
  background: var(--surface-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.75rem 0.9rem;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  line-height: 1.6;
  overflow-x: auto;
  margin: 0;
}

/* ── The measure ────────────────────────────────────────────────────────────
   The signature. Every proportion in the app — a keyword's share of postings,
   the make-up of a staging batch — is drawn on the same tick-ruled baseline,
   so a share always looks like a share. Ticks every 10%.
   ------------------------------------------------------------------------ */

.measure {
  position: relative;
  height: 7px;
  margin-top: 0.3rem;
  background-image: linear-gradient(90deg, var(--rule-strong) 1px, transparent 1px);
  background-size: 10% 100%;
  border-bottom: 1px solid var(--rule-strong);
}

.measure__fill {
  height: 100%;
  background: var(--pine);
  min-width: 1px;
}

/* ── Badges ─────────────────────────────────────────────────────────────── */

.badge {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.15rem 0.4rem;
  border-radius: 2px;
  vertical-align: 0.1em;
  white-space: nowrap;
}

.badge.open, .badge.new { background: var(--pine-tint); color: var(--pine-deep); }
.badge.closed { background: #dcdfdd; color: #414b48; }
.badge.matched { background: var(--oxblood-tint); color: var(--oxblood); }
.badge.duplicate { background: var(--amber-tint); color: var(--amber); }

/* A mark is the user's own — outlined, so it never reads as posting state. */
.mark {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.07em;
  text-transform: uppercase;
  padding: 0.1rem 0.4rem;
  border: 1px solid var(--pine);
  border-radius: 2px;
  color: var(--pine);
}

.tag {
  display: inline-block;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  background: var(--pine-wash);
  color: var(--pine-deep);
  border: 1px solid var(--pine-tint);
  border-radius: 2px;
  padding: 0.1rem 0.4rem;
  margin: 0 0.2rem 0.3rem 0;
}

/* Keyword chips link to a Google search for the term; keep the chip look
   rather than the default link underline, with a quiet hover to signal it. */
a.tag { text-decoration: none; transition: border-color 0.12s ease, color 0.12s ease; }
a.tag:hover { border-color: var(--pine); color: var(--oxblood); }

/* ── Controls ───────────────────────────────────────────────────────────── */

input[type="text"], input[type="file"], textarea, select {
  font: inherit;
  font-size: 0.875rem;
  color: var(--ink);
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  padding: 0.4rem 0.55rem;
}
input[type="text"]:focus, textarea:focus, select:focus {
  outline: none;
  border-color: var(--pine);
  box-shadow: 0 0 0 3px var(--pine-tint);
}
/* Mono is for data, not for prose: a textarea only gets it when what you type
   into it is a list of machine things (URLs), not a sentence about yourself. */
textarea.mono-input { font-family: var(--font-mono); font-size: 0.8125rem; }
::placeholder { color: var(--ink-faint); }

.btn {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  padding: 0.42rem 0.85rem;
  border: 1px solid var(--pine);
  border-radius: var(--radius);
  background: var(--pine);
  color: #fff;
  cursor: pointer;
  white-space: nowrap;
}
.btn:hover { background: var(--pine-deep); border-color: var(--pine-deep); }

.btn-secondary { background: var(--surface); color: var(--ink); border-color: var(--rule-strong); }
.btn-secondary:hover { background: var(--surface-sunk); border-color: var(--ink-faint); }

.btn-danger { background: var(--surface); color: var(--oxblood); border-color: #d9b6ba; }
.btn-danger:hover { background: var(--oxblood); color: #fff; border-color: var(--oxblood); }

button:not([class]) {
  font: inherit;
  font-size: 0.8125rem;
  font-weight: 600;
  padding: 0.42rem 0.85rem;
  border: 1px solid var(--pine);
  border-radius: var(--radius);
  background: var(--pine);
  color: #fff;
  cursor: pointer;
}
button:not([class]):hover { background: var(--pine-deep); }

/* Icon buttons */
.icon-btn {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  padding: 0.3rem;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  color: var(--ink-muted);
  cursor: pointer;
  line-height: 0;
}
.icon-btn:hover { color: var(--pine); border-color: var(--pine); background: var(--pine-wash); }
.icon-btn.btn-delete { color: var(--ink-muted); }
.icon-btn.btn-delete:hover { color: #fff; background: var(--oxblood); border-color: var(--oxblood); }
.icon { width: 14px; height: 14px; display: block; }

/* ── Filter bar ─────────────────────────────────────────────────────────────
   Filters are a row of switches, not a paragraph of links: each facet gets a
   mono label and its options sit in a segmented control.
   ------------------------------------------------------------------------ */

.facets {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.5rem 1.5rem;
}

.facet { display: flex; align-items: center; gap: 0.5rem; }

.facet__label {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
}

.segmented {
  display: inline-flex;
  border: 1px solid var(--rule-strong);
  border-radius: var(--radius);
  overflow: hidden;
  background: var(--surface);
}

.segmented > * {
  padding: 0.28rem 0.6rem;
  font-size: 0.8125rem;
  font-weight: 500;
  color: var(--ink-muted);
  text-decoration: none;
  border-right: 1px solid var(--rule);
  white-space: nowrap;
}
.segmented > *:last-child { border-right: 0; }
.segmented a:hover { background: var(--pine-wash); color: var(--pine-deep); }
.segmented > strong { background: var(--pine); color: #fff; font-weight: 600; }

/* ── Tables ─────────────────────────────────────────────────────────────── */

.table-wrap {
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  overflow-x: auto;
}

table.data { border-collapse: collapse; width: 100%; }

table.data th {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-muted);
  text-align: left;
  padding: 0.55rem 0.75rem;
  background: var(--surface-sunk);
  border-bottom: 1px solid var(--rule-strong);
  white-space: nowrap;
}

table.data td {
  padding: 0.55rem 0.75rem;
  border-bottom: 1px solid var(--rule);
  vertical-align: top;
}
table.data tbody tr:last-child td { border-bottom: 0; }
table.data tbody tr:hover { background: var(--pine-wash); }

table.data td.num, table.data th.num { text-align: right; font-family: var(--font-mono); font-size: 0.8125rem; }

table.data tfoot td {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  background: var(--surface-sunk);
  border-top: 1px solid var(--rule-strong);
  border-bottom: 0;
}

.mono { font-family: var(--font-mono); font-size: 0.8125rem; }
.truncate { max-width: 42ch; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

/* ── Opportunity cards ──────────────────────────────────────────────────── */

.jobs-grid {
  display: grid;
  gap: 0.875rem;
  grid-template-columns: 1fr;
  align-items: start;
}
@media (min-width: 640px)  { .jobs-grid { grid-template-columns: repeat(2, 1fr); } }
@media (min-width: 1024px) { .jobs-grid { grid-template-columns: repeat(3, 1fr); } }
@media (min-width: 1400px) { .jobs-grid { grid-template-columns: repeat(4, 1fr); } }

.job {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.875rem;
  height: 100%;
}
.job:hover { border-color: var(--rule-strong); box-shadow: var(--shadow); }

/* Closed postings stay legible but stop competing for attention. */
.job.is-closed { background: #f1f1ec; }
.job.is-closed .job__title a { color: var(--ink-muted); }

.job__head { display: flex; align-items: flex-start; justify-content: space-between; gap: 0.5rem; }

.job__title {
  font-family: var(--font-display);
  font-size: 1rem;
  font-weight: 700;
  letter-spacing: -0.015em;
  line-height: 1.25;
}
.job__title a { color: var(--ink); text-decoration: none; }
.job__title a:hover { color: var(--pine); text-decoration: underline; }

.job__company { font-size: 0.8125rem; font-weight: 600; }
.job__company a { color: var(--pine); text-decoration: none; }
.job__company a:hover { text-decoration: underline; }

.job__meta {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--ink-faint);
  line-height: 1.5;
}
.job__meta strong { color: var(--ink-muted); font-weight: 500; }

.job__keywords { margin-top: auto; padding-top: 0.25rem; }
.job__keywords .tag { font-size: 0.6875rem; }

.job__foot {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  padding-top: 0.5rem;
  border-top: 1px solid var(--rule);
}

.job-link {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  white-space: nowrap;
  text-decoration: none;
  color: var(--pine);
}
.job-link:hover { text-decoration: underline; }

/* The link and its source host read as one unit, host trailing quietly. */
.job-link-wrap { display: inline-flex; align-items: baseline; gap: 0.5rem; min-width: 0; }
.job-link__domain {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  color: var(--ink-faint);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

/* A company band: the heading spans every column, so the cards below visibly
   belong to it rather than merely following it. */
.group-head {
  grid-column: 1 / -1;
  display: flex;
  align-items: baseline;
  gap: 0.5rem;
  margin: 1.5rem 0 0;
  padding-bottom: 0.4rem;
  border-bottom: 2px solid var(--pine);
  font-family: var(--font-display);
  font-size: 1.0625rem;
  font-weight: 700;
  letter-spacing: -0.02em;
}
.group-head:first-child { margin-top: 0; }
.group-count {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 500;
  color: var(--ink-faint);
}

.load-more { margin-top: 1.5rem; text-align: center; }
#scroll-status {
  margin-top: 0.75rem;
  text-align: center;
  font-family: var(--font-mono);
  font-size: 0.75rem;
  color: var(--ink-faint);
}

/* ── Flash ──────────────────────────────────────────────────────────────── */

.flash {
  display: flex;
  gap: 0.6rem;
  background: var(--pine-wash);
  border: 1px solid var(--pine-tint);
  border-left: 3px solid var(--pine);
  border-radius: var(--radius);
  padding: 0.7rem 0.9rem;
  font-size: 0.875rem;
  margin-bottom: 1rem;
}

/* Toasts — the same message as the banner above, but where the eye is. Dark,
   like the keyword tooltip, so it reads as a passing notice rather than another
   panel competing with the page. */
.toasts {
  position: fixed;
  right: 1.25rem;
  bottom: 1.25rem;
  z-index: 200;
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  width: min(26rem, calc(100vw - 2.5rem));
  pointer-events: none;   /* only the toasts themselves take clicks */
}
.toast {
  pointer-events: auto;
  cursor: pointer;
  background: var(--ink);
  color: #e8ece9;
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 0.7rem 0.9rem;
  font-size: 0.875rem;
  line-height: 1.45;
  opacity: 0;
  transform: translateY(0.4rem);
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast--in { opacity: 1; transform: none; }

@media (prefers-reduced-motion: reduce) {
  .toast { transition: none; }
}

.empty {
  color: var(--ink-muted);
  font-size: 0.875rem;
  padding: 1.25rem;
  border: 1px dashed var(--rule-strong);
  border-radius: var(--radius);
  text-align: center;
}

/* ── Responsive ─────────────────────────────────────────────────────────── */

@media (max-width: 860px) {
  .shell { display: block; }
  .rail {
    position: static;
    width: auto;
    height: auto;
    flex-direction: row;
    align-items: center;
    gap: 1rem;
    padding: 0.75rem 1rem;
    overflow-x: auto;
  }
  .rail__mark { padding: 0; white-space: nowrap; }
  .rail__mark span { display: inline; }
  .rail__nav { flex-direction: row; gap: 0.25rem; }
  .rail__foot { display: none; }
  .main { padding: 1.25rem 1rem 3rem; }
}

@media (prefers-reduced-motion: reduce) {
  *, *::before, *::after {
    animation-duration: 0.01ms !important;
    transition-duration: 0.01ms !important;
    scroll-behavior: auto !important;
  }
}

/* ── The CV sheet ─────────────────────────────────────────────────────
   Shared by the app's CV page and the standalone public page, which is
   why it lives here rather than in a page's own <style>. */

.cv-sheet {
  max-width: 54rem;
  margin: 0 auto;
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  box-shadow: var(--shadow);
  padding: 3rem 3.25rem 3.5rem;
}
@media (max-width: 720px) { .cv-sheet { padding: 1.5rem 1.25rem 2rem; } }

.cv-name {
  font-family: var(--font-display);
  font-weight: 800;
  font-size: 2rem;
  letter-spacing: -0.02em;
  margin: 0;
  color: var(--ink);
}
.cv-contact {
  margin: 0.4rem 0 0;
  font-size: 0.875rem;
  color: var(--ink-muted);
}
.cv-contact a { color: inherit; }
.cv-contact .sep { color: var(--ink-faint); padding: 0 0.35rem; }

/* Sections are the sheet's coarsest division, so their separation has to
   beat every gap inside one. Mirrors SECTION_SPACE in docx_export, so a
   printed page and a Word download are spaced alike. */
.cv-section { margin-top: 3rem; }
.cv-section > h2 {
  font-family: var(--font-mono);
  font-size: 0.75rem;
  font-weight: 600;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--pine-deep);
  border-bottom: 1px solid var(--rule);
  padding-bottom: 0.4rem;
  margin: 0 0 0.9rem;
}
/* A view can highlight a section: a quiet amber rule that draws the eye
   without shouting — the sheet stays a document, not a dashboard. */
.cv-section--hl { border-left: 3px solid var(--amber); padding-left: 1rem; }

.cv-summary { margin: 0; line-height: 1.6; }

/* Skills: the source document's label/value hanging layout, as a grid. */
.cv-skills { display: grid; grid-template-columns: 8.5rem 1fr; gap: 0.45rem 1rem; margin: 0; }
.cv-skills dt { font-weight: 600; color: var(--oxblood); font-size: 0.875rem; }
.cv-skills dd { margin: 0; font-size: 0.9375rem; line-height: 1.55; color: var(--ink); }
/* One line, twice: the working sheet with every competency in its column,
   and the plain line a printed copy carries — which is a document you would
   send, and so leaves out what you are still acquiring (_cv_sheet.html). */
.only-in-print { display: none; }
@media print {
  .only-on-screen { display: none !important; }
  .only-in-print { display: inline; }

  /* Printing an app page prints the document it is showing, not the app.
     The rail, the page's own controls and the frame the sheet sits in are
     furniture for working; on paper they cost a third of the width, and a
     third of the width is a third more lines and the pages that follow
     from them. The public page has always stripped itself this way (see
     cv_public.html) — this is the same, for the sheet you print from /cv. */
  .rail { display: none; }
  .main { padding: 0; }
  .main__inner { max-width: none; }
  body { background: #fff; }
  .cv-sheet {
    max-width: none;
    padding: 0;
    border: 0;
    border-radius: 0;
    box-shadow: none;
  }
}

/* Where a skill sits on the profile board, said on the CV's own pages: the
   sheet at /cv and the competency rows in both editors, which list the word
   rather than offering a field over it — so one rule over a <span> carries
   all three. The three colours are the board's own (see profile.html), so
   one glance carries between them.

   The colour is the **ground**, with the page's paper as the ink: a skill
   here is drawn the way a card on the board is drawn, and for the same
   reason. Laid on at a tenth it was a tint, and a line of a dozen skills in
   three tints is a line you have to read word by word; as a block of colour
   the column is the shape, and the line sorts itself at a glance.

   Only the app's pages. Everything that leaves renders the document
   instead — the public sheet is never marked up at all, the Word export
   builds from the data and never sees a stylesheet, and the print reset
   below takes the ground off, because a CV printed to PDF from /cv has to
   come out as the thing you would send. */
.cv-skill--strong { --skill-ink: var(--surface); --skill-ground: var(--pine); }
.cv-skill--some_experience { --skill-ink: var(--surface); --skill-ground: var(--pine-soft); }
.cv-skill--would_like_to_learn { --skill-ink: var(--surface); --skill-ground: var(--rust); }

/* A skill with no column has neither property, so it inherits the line it
   is on and sits on nothing — which is the tell that it needs linking. */
.cv-skill {
  color: var(--skill-ink, inherit);
  background: var(--skill-ground, transparent);
}
/* The room a block of colour needs around its word. Real padding rather
   than a negative margin: cancelling it lets two grounds meet across the
   separator, and two skills of one colour then read as a single block. */
.cv-skill { padding: 0.1rem 0.4rem; border-radius: 2px; }

/* The document, back. The padding goes with the colour: kept, it would hold
   the line open where a block used to be and print as loose word spacing. */
@media print {
  .cv-skill { color: inherit; background: transparent; padding: 0; }
}
@media (max-width: 560px) { .cv-skills { grid-template-columns: 1fr; gap: 0.1rem 0; } .cv-skills dd { margin-bottom: 0.5rem; } }

.cv-cert { margin: 0 0 0.75rem; }
.cv-cert__head { font-weight: 600; font-size: 0.9375rem; }
.cv-cert__head .detail { font-weight: 400; color: var(--ink-muted); }
.cv-cert__items { margin: 0.15rem 0 0; font-size: 0.875rem; color: var(--ink-muted); line-height: 1.55; }

.cv-entry { margin: 0 0 1.4rem; }
.cv-entry__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 1rem;
}
.cv-entry__company { font-family: var(--font-display); font-weight: 700; font-size: 1.0625rem; letter-spacing: -0.01em; }
.cv-entry__company .descriptor { font-family: var(--font-body); font-weight: 400; font-size: 0.875rem; color: var(--ink-muted); }
.cv-entry__dates { font-family: var(--font-mono); font-size: 0.8125rem; color: var(--ink-faint); flex: none; }

.cv-role { margin: 0.35rem 0 0; }
.cv-role__head { display: flex; align-items: baseline; justify-content: space-between; gap: 1rem; }
.cv-role__title { font-weight: 600; font-size: 0.9375rem; color: var(--oxblood); }
.cv-role__dates { font-family: var(--font-mono); font-size: 0.75rem; color: var(--ink-faint); flex: none; }
.cv-role ul { margin: 0.3rem 0 0; padding: 0; list-style: none; }
.cv-role li { position: relative; padding-left: 1.1rem; margin-bottom: 0.3rem; line-height: 1.55; font-size: 0.9375rem; }
.cv-role li::before { content: "–"; position: absolute; left: 0; color: var(--oxblood); font-weight: 600; }

.cv-education { margin: 0; line-height: 1.6; font-size: 0.9375rem; }
.cv-education .lead { font-weight: 600; }
.cv-langs { margin: 0; }
.cv-langs div { font-size: 0.9375rem; line-height: 1.6; }
.cv-langs .label { font-weight: 600; }
.cv-langs .detail { color: var(--ink-muted); }

/* Printing the sheet (the public page's Print / PDF button): never leave a
   heading stranded at a page break, and move a role between pages as one
   block — the print mirror of the Word export's keep-with-next rules. */
@media print {
  .cv-section > h2,
  .cv-entry__head,
  .cv-role__head,
  .cv-cert__head:not(:last-child) { break-after: avoid; page-break-after: avoid; }
  .cv-cert { break-inside: avoid; page-break-inside: avoid; }
  .cv-role li { break-inside: avoid; page-break-inside: avoid; }

  /* A section short enough to fit a page reads as one thing, so it moves
     whole rather than splitting two lines across the fold — the whitespace
     that leaves behind costs less than the split does. Career history is
     deliberately absent: it is long by nature and breaks at its roles. */
  .cv-section--summary,
  .cv-section--skills,
  .cv-section--education,
  .cv-section--languages { break-inside: avoid; page-break-inside: avoid; }

  /* Printed, the sheet is a document, and prose in a document is set flush
     to both edges. Only the prose: a company line and a role line each end
     in a date pushed to the right margin, and a skills line is a list of
     names — justifying either opens gaps where the eye reads structure
     rather than sentences. On screen it stays ranged left, which is what
     reads well in a browser and is what the page has always done. */
  .cv-summary,
  .cv-role li { text-align: justify; }
}

/* ── The career editor ────────────────────────────────────────────────
   Shared by the master's career page and a view's overlay, because both
   show the same three-level tree — companies, roles, items — and differ
   only in which controls hang off each row. Indentation carries the
   nesting; a hidden record stays visible but recedes, since a record you
   can't see is one you can't bring back. */

.rec {
  border-left: 2px solid var(--rule);
  padding-left: 0.7rem;
  margin: 0.5rem 0 0;
}
.rec--company { border-left-color: var(--pine); margin-top: 0.9rem; }
.rec--role { border-left-color: var(--oxblood); margin-left: 0.2rem; }
.rec--item { border-left-color: var(--rule); margin-left: 0.2rem; }

.rec__head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  gap: 0.75rem;
}

/* The drag handle. Hidden until the drag script marks the tree, so a page
   without JavaScript never offers a grip that does nothing. It is also a
   control: focus it and the arrow keys move the record, which is the whole
   of the keyboard path now that the step buttons are gone. */
.rec__grip { display: none; }
.is-draggable .rec__grip {
  display: inline-block;
  flex: none;
  align-self: center;
  cursor: grab;
  color: var(--ink-faint);
  font-size: 0.875rem;
  line-height: 1;
  padding: 0.2rem 0.15rem;
  border-radius: var(--radius);
  user-select: none;
}
.is-draggable .rec__grip:hover { color: var(--pine-deep); }
.is-draggable .rec__grip:focus-visible {
  outline: 2px solid var(--pine-tint);
  color: var(--pine-deep);
}
.rec.is-dragging { opacity: 0.45; cursor: grabbing; }
.rec.is-dragging .rec__grip { cursor: grabbing; }
/* The label takes the whole middle of the row. Without this it is only as
   wide as its text, and a row of grip + label + tools under space-between
   pushes that text into the middle of the gap — so a short item read as
   centred while a long one wrapped to the full width. Filling the space
   lines every row up on the same left edge. min-width:0 lets it wrap
   rather than force the row wider than its card. */
.rec__name, .rec__text { flex: 1; min-width: 0; text-align: left; }
.rec__name { font-weight: 600; font-size: 0.9375rem; }
.rec--company > .rec__head .rec__name { font-family: var(--font-display); font-size: 1.0625rem; }
.rec--role > .rec__head .rec__name { color: var(--oxblood); }
.rec__text { font-size: 0.9375rem; line-height: 1.5; }
.rec__meta { font-family: var(--font-mono); font-size: 0.75rem; font-weight: 400; color: var(--ink-faint); }

/* Competencies stack one per row, like every other record. They were laid
   out two-up to save height, but a wrapping grid cannot be reordered
   legibly: a horizontal drop line can only say "before this record", so
   "after the last one on a row" had to be drawn above the first record of
   the next row — the line appearing to jump columns for no reason. One
   record per row makes every insertion point a plain rule between two of
   them. They stay compact: a chip is padding, not a card. */
.rec__list--chips,
.rec--company > .rec__list:has(> .rec--chip) {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
  margin: 0.4rem 0 0 0.2rem;
}
.rec--chip {
  border: 1px solid var(--rule);
  border-left: 2px solid var(--rule-strong);
  border-radius: var(--radius);
  background: var(--surface);
  padding: 0.15rem 0.4rem;
  margin: 0;
}
.rec--chip > .rec__head { gap: 0.35rem; }
.rec--chip .rec__text { font-size: 0.8125rem; }

/* Hidden here means "left out of this CV", not "deleted" — it reads as a
   struck-through record you can put back, never as a missing one. */
.rec.is-hidden > .rec__head .rec__name,
.rec.is-hidden > .rec__head .rec__text {
  color: var(--ink-faint);
  text-decoration: line-through;
}

/* Where the dragged record will land. Drawn in the flow at the insertion
   point, so it reads as a gap opening up rather than an overlay: a rule
   across a stack of records, a bar between two chips on a row. */
.rec__drop {
  position: absolute;
  border-radius: 2px;
  background: var(--pine);
  pointer-events: none;
}
/* The marker anchors to its own list, and being out of flow it can never
   reflow the records it is describing. */
.rec__list { position: relative; }

.rec__tools { display: flex; gap: 0.25rem; flex: none; align-items: center; }
.rec__tools form { margin: 0; }
.rec__tools button {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.06em;
  color: var(--ink-muted);
  background: var(--surface);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.15rem 0.4rem;
  cursor: pointer;
  line-height: 1.4;
}
.rec__tools button:hover { color: var(--pine-deep); border-color: var(--rule-strong); }
.rec__tools button.danger:hover { color: var(--oxblood); border-color: var(--oxblood); }

.career__earlier { display: flex; align-items: center; gap: 0.5rem; flex-wrap: wrap; margin: 0.75rem 0 0; }
.career__earlier label { font-size: 0.8125rem; color: var(--ink-muted); }
.career__earlier .cv-edit__save { margin-top: 0; }

/* Where Earlier Experience starts — the same line the CV draws, shown in
   the editor so the split is visible while you reorder. */
.career__divider {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
  color: var(--ink-faint);
  border-top: 1px dashed var(--rule-strong);
  margin: 1.25rem 0 0;
  padding-top: 0.5rem;
}

/* ── The capability matrix ────────────────────────────────────────────
   Categories are grid columns, engagements are grid rows, and a merged run
   of rows is one block spanning them — the swim lanes. Only the per-category
   colours are inline in the markup, because they are data; everything about
   the shape lives here. Shared by /cv and the public sheet. */

.cvchart { margin: 0; }

.cvchart__legend {
  display: flex;
  align-items: center;
  gap: 1.1rem;
  margin: 0 0 0.85rem;
  font-size: 0.8125rem;
  color: var(--ink-muted);
}
.cvchart__legend-title {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  text-transform: uppercase;
  letter-spacing: 0.14em;
}
.cvchart__key { display: inline-flex; align-items: center; gap: 0.4rem; color: var(--ink); }
.cvchart__swatch {
  width: 1.15rem;
  height: 0.85rem;
  border-radius: 3px;
  flex: none;
}
/* The legend explains the encoding, not a category, so its swatches are
   neutral ink rather than any one hue. */
.cvchart__swatch--core { background: #3a3a37; }
.cvchart__swatch--involved { background: #dcdcda; border: 2px solid #3a3a37; }

.cvchart__grid {
  display: grid;
  /* The label column takes 80% of what it used to, here as in the printed
     chart and the Word image: the fifth it gives back is what the category
     columns need for their names. */
  grid-template-columns: minmax(7.2rem, 12rem) repeat(var(--cvchart-cols), minmax(0, 1fr));
  grid-auto-rows: minmax(2.6rem, auto);
  column-gap: 0;
  align-items: stretch;
}

.cvchart__head {
  grid-row: 1;
  /* Tops aligned, not bottoms: a name that wraps grows down towards the
     rows it labels, and the row of heads reads as one line of labels
     rather than as a ragged edge. */
  align-self: start;
  padding: 0 0.45rem 0.45rem;
  text-align: center;
  font-weight: 700;
  /* Sized for the column it sits in rather than for the sheet: seven
     categories leave each head a few centimetres, and at the sheet's own
     size "Development" and "Consulting" ran into each other on screen the
     way "Marketing Automation" and "ERP" did on paper. `anywhere` is the
     backstop for a single word longer than its column — a broken word is
     legible, two words on top of each other are not. */
  font-size: 0.6875rem;
  line-height: 1.25;
  overflow-wrap: anywhere;
}

.cvchart__label {
  grid-column: 1;
  display: flex;
  flex-direction: column;
  justify-content: center;
  padding: 0.4rem 0.9rem 0.4rem 0;
  border-top: 1px solid var(--rule);
}
.cvchart__company { font-weight: 700; font-size: 0.9375rem; line-height: 1.2; }
.cvchart__role { font-size: 0.8125rem; color: var(--ink-muted); line-height: 1.3; }
.cvchart__years {
  font-family: var(--font-mono);
  font-size: 0.6875rem;
  font-variant-numeric: tabular-nums;
  color: var(--ink-faint);
}

/* 7px of margin each side is the ~14px gap between stacked blocks. */
.cvchart__block {
  margin: 7px 5px;
  border-radius: 8px;
  box-sizing: border-box;
  min-height: 1.6rem;
}

@media (max-width: 640px) {
  /* The head size is the base size now, so only the label column and the
     blocks still need narrowing here. A phone cannot give seven categories
     room for their names whatever this says: below about 900px the heads
     fall back on breaking a word, which is the graceful end of the same
     rule rather than a second one. */
  .cvchart__grid { grid-template-columns: minmax(6rem, 8rem) repeat(var(--cvchart-cols), minmax(0, 1fr)); }
  .cvchart__block { margin: 5px 3px; }
}

/* Printing: a chart is a single figure, so never break one across pages,
   and keep its heading with it. The involved blocks survive greyscale on
   their borders alone — which is why the border is not decoration. */
@media print {
  .cv-section--chart { break-inside: avoid; page-break-inside: avoid; }
  .cvchart__block { -webkit-print-color-adjust: exact; print-color-adjust: exact; }

  /* The label column at 80% of what it took, which is the fifth the
     category columns needed. What that buys is smaller than it sounds:
     spread over seven columns it is about 3px each, so the size the heads
     can be is set by the page, not by this. Seven categories across the
     17cm text column leaves each about 1.7cm, and "Intelligence" is 2cm at
     8pt — measured in the rendered PDF, where 7pt is the largest that keeps
     every category name whole. `anywhere` stays as the backstop for a name
     longer still: a broken word is legible, two words on top of each other
     are not. */
  .cvchart__head { font-size: 7pt; line-height: 1.2; padding: 0 3pt 4pt; }
}

/* A skill your CV answers, on a job card: the tag reverses - pine ground,
   pale letters, bold - so scanning the list shows at a glance where you
   have something to say. Read through the whole family, so a card marks
   "REST APIs" when the CV holds APIs. */
.tag--held {
  background: var(--pine);
  border-color: var(--pine-deep);
  color: var(--surface);
  font-weight: 700;
}
.tag--held:hover { background: var(--pine-deep); color: var(--surface); }

/* A skill you have placed on the profile board wears that column's colour
   here too, so the board and the postings read as one thing: a card tells
   you what a role wants *and* what you said about yourself.

   These come after .tag--held on purpose. A skill can be both answered by
   the CV and placed, and a tag has one ground to give: the placement is the
   claim you made yourself, so it takes it. A skill placed in a column with
   no colour — No experience — leaves the held mark standing. */
.tag--strong,
.tag--some_experience,
.tag--would_like_to_learn {
  color: var(--surface);
  font-weight: 700;
}
.tag--strong { background: var(--pine); border-color: var(--pine-deep); }
.tag--some_experience { background: var(--pine-soft); border-color: #23594e; }
.tag--would_like_to_learn { background: var(--rust); border-color: #7a3a09; }
.tag--strong:hover,
.tag--some_experience:hover,
.tag--would_like_to_learn:hover { color: var(--surface); }
.tag--strong:hover { background: var(--pine-deep); }
.tag--some_experience:hover { background: #26655a; }
.tag--would_like_to_learn:hover { background: #8c4209; }

/* ── The merge dialog ─────────────────────────────────────────────────────
   Shared by the dashboard's skill cloud and the profile board: both let you
   drag one skill onto another, and both mean the same thing by it, so both
   open this. Markup in _merge_dialog.html, behaviour in js/merge_dialog.js.
   ───────────────────────────────────────────────────────────────────────── */
/* ── Merge dialog ───────────────────────────────────────────────────── */
.modal-overlay {
  display: none;
  position: fixed;
  inset: 0;
  background: rgba(20, 32, 29, 0.5);
  z-index: 100;
  justify-content: center;
  align-items: center;
  padding: 1rem;
}
.modal-overlay.open { display: flex; }
.modal {
  background: var(--surface);
  border-radius: var(--radius);
  padding: 1.5rem;
  max-width: 520px;
  width: 100%;
  box-shadow: 0 16px 48px rgba(20, 32, 29, 0.3);
}
.modal h2 {
  font-family: var(--font-display);
  font-size: 1.25rem;
  letter-spacing: -0.02em;
  margin-bottom: 0.25rem;
}
.merge-pair { display: flex; gap: 0.75rem; align-items: stretch; margin: 1rem 0; }
.merge-side {
  flex: 1;
  min-width: 0;
  background: var(--surface-sunk);
  border: 1px solid var(--rule);
  border-radius: var(--radius);
  padding: 0.6rem 0.7rem;
}
/* A side is a choice, so the whole of it is the target — and the one whose
   name is being kept says so, since the field below can be typed into and
   would otherwise be the only place the answer showed. */
label.merge-side { cursor: pointer; }
.merge-side:has(input:checked) {
  border-color: var(--pine);
  background: var(--pine-wash);
  box-shadow: inset 0 0 0 1px var(--pine);
}
.merge-side h3 input { margin: 0 0.35rem 0 0; vertical-align: -0.1em; }
.merge-side h3 {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--ink-faint);
  margin-bottom: 0.3rem;
}
.merge-side strong { display: block; margin-bottom: 0.4rem; word-break: break-word; }
.merge-plus {
  align-self: center;
  font-family: var(--font-mono);
  font-size: 1.1rem;
  color: var(--ink-faint);
}
.modal .variant-tag {
  display: inline-block;
  font-family: var(--font-mono);
  background: var(--surface);
  border: 1px solid var(--rule-strong);
  border-radius: 2px;
  padding: 0.05rem 0.35rem;
  margin: 0.15rem 0.15rem 0 0;
  font-size: 0.6875rem;
}
.result-section {
  background: var(--pine-wash);
  border: 1px solid var(--pine-tint);
  border-radius: var(--radius);
  padding: 0.6rem 0.7rem;
  margin: 1rem 0;
}
.result-section h3 {
  font-family: var(--font-mono);
  font-size: 0.625rem;
  font-weight: 600;
  letter-spacing: 0.08em;
  text-transform: uppercase;
  color: var(--pine);
  margin-bottom: 0.3rem;
}
.result-section .variant-tag { border-color: var(--pine-tint); }
.modal label { display: block; margin: 1rem 0 0.3rem; font-weight: 600; font-size: 0.8125rem; }
.modal input[type="text"] { width: 100%; }
.modal .btn-row { display: flex; gap: 0.5rem; justify-content: flex-end; margin-top: 1.25rem; }

/* ── The document's own measure ───────────────────────────────────────────
   Last in the file on purpose: these are the same selectors the sheet is
   built from, so the only thing that lets them win is coming after. A print
   rule written beside the others further up is silently overridden by the
   screen rule below it — which is how the printed CV kept the screen's type
   while claiming to be set as the document.
   ------------------------------------------------------------------------ */
@media print {
/* The page the document is printed on, and the type it is set in, are
   the Word export's: A4, 1.6cm by 2cm margins, 10pt on tight leading with
   21pt above a section heading (see docx_export.py, which is where those
   numbers come from). The sheet's own scale is a screen scale — 11.25pt
   on 1.55, three centimetres above every heading — and on paper it cost a
   quarter of the text per page, which is a page for every four. The two
   files should be the same document, so they are set the same. */
@page { size: A4; margin: 1.6cm 2cm; }

/* The app's ink is a near-black with green in it: right on a screen, grey
   on paper. A printed CV is a document — photocopied, sent on, opened in a
   reader that is not this one — so its text is black. Redefining the token
   carries it to everything that asks for var(--ink); the two greys that mean
   something (a date, a detail beside a title) and the oxblood accents are
   left exactly as they are, being hierarchy rather than body text. */
.cv-sheet { --ink: #000; color: #000; }

/* 1.28, not the 1.35 this started at: the export sets its lines at about
   1.15 and the two documents were drifting a section apart by the third
   page — Education and Languages sat at the foot of the Word file's page 3
   and at the head of the printed page 4. Measured against the real CV,
   this is where the fold falls in the same place in both. */
.cv-section > h2 { color: #000; }

.cv-sheet { font-size: 10pt; line-height: 1.28; }
.cv-name { font-size: 20pt; }
.cv-contact { font-size: 9pt; margin-top: 4pt; }
.cv-section { margin-top: 21pt; }
.cv-section > h2 { font-size: 8.5pt; margin-bottom: 8pt; }
.cv-summary { font-size: 10pt; line-height: 1.28; }
.cv-skills { gap: 4pt 10pt; grid-template-columns: 3.4cm 1fr; }
.cv-skills dt { font-size: 9.5pt; }
.cv-skills dd { font-size: 9.5pt; line-height: 1.28; }
.cv-cert { margin-bottom: 8pt; }
.cv-cert__head { font-size: 10pt; }
.cv-cert__items { font-size: 9pt; line-height: 1.28; }
.cv-entry { margin-bottom: 12pt; }
.cv-entry__company { font-size: 11pt; }
.cv-entry__company .descriptor,
.cv-entry__dates { font-size: 9pt; }
.cv-role { margin-top: 4pt; }
.cv-role__title { font-size: 10pt; }
.cv-role__dates { font-size: 8.5pt; }
.cv-role ul { margin-top: 3pt; }
.cv-role li { font-size: 10pt; line-height: 1.28; margin-bottom: 3pt; }
.cv-education, .cv-languages { font-size: 10pt; line-height: 1.28; }
}
