/* Shared chrome for the course tools.
 *
 * The design system is the one Andrew already built for the MKTG 729
 * Competitive Intelligence Dashboard: Sora and DM Mono, warm neutrals, and a
 * light/dark pair driven off
 * data-theme on <body>. Every tool draws from the tokens below, so a course
 * app looks like the rest of the set rather than like whichever product it
 * happens to imitate.
 *
 * Fonts are self-hosted rather than pulled from Google, matching the rest of
 * the site: these pages make no third-party requests. DM Mono is OFL.
 */

/* Fonts are self-hosted, never linked from Google.
 *
 * Two reasons, and the second is the one that matters for a course tool.
 * Embedding Google's stylesheet sends every visitor's IP to Google, which a
 * German court found unlawful under the GDPR in 2022. And fonts.googleapis.com
 * is unreachable on plenty of networks — China blocks it, and so do some
 * corporate and campus firewalls — so a student's page would fall back to a
 * system font through no fault of their own. Serving from this origin means
 * the type works wherever the page works.
 *
 * Each family is split by unicode-range, so a page pulls the latin file and
 * nothing else until a glyph actually needs the rest. A student typing a name
 * with Polish or Vietnamese diacritics gets it set in the real face rather
 * than a mismatched fallback, and everyone else never downloads those bytes.
 * Greek and Cyrillic are deliberately not carried; they fall back to the
 * system font, which stays perfectly legible.
 *
 * Weight ranges below match what the files actually contain — Sora is 400–800
 * and Inter is 100–900. Declaring a range the file cannot serve makes the
 * browser silently clamp, which is how Sora 300 was rendering as 400.
 *
 * Licences: all three are SIL OFL 1.1. The full texts sit beside the files in
 * fonts/, because Google's subset builds strip the embedded licence and the
 * OFL requires it to travel with the font.
 */

@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/dm-mono-400-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 400;
  font-display: swap;
  src: url('/fonts/dm-mono-400-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/dm-mono-500-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'DM Mono';
  font-style: normal;
  font-weight: 500;
  font-display: swap;
  src: url('/fonts/dm-mono-500-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/inter-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/inter-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}
@font-face {
  font-family: 'Inter';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('/fonts/inter-vietnamese.woff2') format('woff2');
  unicode-range: U+0102-0103, U+0110-0111, U+0128-0129, U+0168-0169, U+01A0-01A1, U+01AF-01B0, U+0300-0301, U+0303-0304, U+0308-0309, U+0323, U+0329, U+1EA0-1EF9, U+20AB;
}
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/sora-latin.woff2') format('woff2');
  unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+0304, U+0308, U+0329, U+2000-206F, U+20AC, U+2122, U+2191, U+2193, U+2212, U+2215, U+FEFF, U+FFFD;
}
@font-face {
  font-family: 'Sora';
  font-style: normal;
  font-weight: 400 800;
  font-display: swap;
  src: url('/fonts/sora-latin-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF, U+0304, U+0308, U+0329, U+1D00-1DBF, U+1E00-1E9F, U+1EF2-1EFF, U+2020, U+20A0-20AB, U+20AD-20C0, U+2113, U+2C60-2C7F, U+A720-A7FF;
}

:root {
  --bg: #f7f6f3;
  --surface: #fff;
  --surface2: #f0eeeb;
  --border: rgba(0, 0, 0, .08);
  --border2: rgba(0, 0, 0, .14);
  --text: #1a1916;
  --text2: #6b6860;
  --text3: #a09e9a;
  --shadow: 0 1px 3px rgba(0, 0, 0, .06), 0 4px 12px rgba(0, 0, 0, .04);
  --r: 10px;
  --rl: 14px;
  /* Sora carries the headings and the brand voice. Inter carries running UI
     text, because Sora's wide letterforms and tight apertures muddy at the
     12–13px the forms actually use. DM Mono carries labels and figures. */
  --display: 'Sora', 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, sans-serif;
  --font: 'Inter', system-ui, -apple-system, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
  --mono: 'DM Mono', ui-monospace, SFMono-Regular, 'SF Mono', Menlo, Consolas, monospace;

  /* The brand constants, Andrew's own since October 2026: the highlighter
     yellow from andrewko.ca and his book, replacing the lime and charcoal the
     dashboard borrowed. They hold their value in both themes, so every pairing
     the lime had survives: ink on yellow and yellow on ink are both 13.4:1.
     Yellow is 1.3:1 on white, so it fills things and never outlines them;
     focus rings use --focus instead. */
  --brand: #efe34a;
  --brand-hover: #ddd03a;
  --ink-deep: #181815;
  --focus: #1a1916;

  /* Semantic washes, carried over from the dashboard's badges. */
  --brand-wash: #fcf8d4;
  --brand-ink: #6e5a00;
  --ok-wash: #e6f5e6;
  --ok-ink: #1a6b1a;
  --bad-wash: #fdecea;
  --bad-ink: #b71c1c;
  --warn-wash: #faf3e4;
  --warn-ink: #7a5a12;
  --warn-icon: #b4780e;
  --warn-rule: rgba(180, 120, 14, .35);

  /* Identity colours for tools that need to tell things apart: ad groups,
     ad sets, the two SERP scores, metric families. A fixed order, validated
     for colour-blind separation on both surfaces (worst adjacent ΔE 9.1 light,
     8.4 dark); use them in this order and never for text, which stays in the
     text tokens. Three sit under 3:1 on white, so a coloured mark always sits
     beside a visible label. Each has a wash for tinted backgrounds. */
  --c1: #2a78d6; --c1-wash: #eaf2fb;
  --c2: #eb6834; --c2-wash: #fdf0eb;
  --c3: #1baf7a; --c3-wash: #e8f7f2;
  --c4: #eda100; --c4-wash: #fdf6e6;
  --c5: #e87ba4; --c5-wash: #fdf2f6;
}

[data-theme=dark] {
  --bg: #111110;
  --surface: #1c1b19;
  --surface2: #242320;
  --border: rgba(255, 255, 255, .08);
  --border2: rgba(255, 255, 255, .14);
  --text: #f0ede8;
  --text2: #9b9790;
  --text3: #625f5a;
  --shadow: 0 1px 3px rgba(0, 0, 0, .3), 0 4px 12px rgba(0, 0, 0, .2);

  --brand-wash: #2a2812;
  --brand-ink: #efe34a;
  --focus: #efe34a;
  --ok-wash: #0f2f0f;
  --ok-ink: #6dc96d;
  --bad-wash: #2d0f0f;
  --bad-ink: #ef9a9a;
  --warn-wash: #2a2410;
  --warn-ink: #e0c07a;
  --warn-icon: #d9a63c;
  --warn-rule: rgba(217, 166, 60, .32);

  --c1: #3987e5; --c1-wash: #223142;
  --c2: #d95926; --c2-wash: #42271c;
  --c3: #199e70; --c3-wash: #1b352a;
  --c4: #c98500; --c4-wash: #3f3014;
  --c5: #d55181; --c5-wash: #41262e;
}

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

/* Held for one frame while the theme flips. See applyTheme() in workspace.js:
   a transitioned property driven by a custom property will not pick up the new
   value when only that custom property changes. */
.theme-switching *,
.theme-switching *::before,
.theme-switching *::after { transition: none !important; }

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

body {
  margin: 0;
  font-variant-numeric: tabular-nums;
  background: var(--bg);
  color: var(--text);
  font-family: var(--font);
  font-size: 15px;
  line-height: 1.6;
  -webkit-font-smoothing: antialiased;
  text-rendering: optimizeLegibility;
  transition: background .25s, color .25s;
}

/* ---------- the bar that sits above a tool ---------- */

/* Sticky and yellow-ruled, the same header treatment the dashboard uses, so the
   two tools read as the same application rather than two one-offs. */
.shell-bar {
  position: sticky;
  top: 0;
  z-index: 100;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem 1.5rem;
  flex-wrap: wrap;
  padding: 0.7rem clamp(1rem, 4vw, 1.5rem);
  background: var(--surface);
  border-bottom: 3px solid var(--brand);
  box-shadow: var(--shadow);
}

.crumbs {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.15rem 0.5rem;
  font-size: 13px;
  color: var(--text2);
  min-width: 0;
}
.crumbs a {
  color: var(--text2);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: color .15s, border-color .15s;
}
.crumbs a:hover, .crumbs a:focus-visible { color: var(--text); border-bottom-color: var(--border2); }
.crumbs .sep { color: var(--text3); }
.crumbs .here { color: var(--text); font-family: var(--display); font-weight: 600; letter-spacing: -.01em; }

.shell-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
  flex-wrap: wrap;
  font-size: 12px;
}

.save-state {
  display: inline-flex;
  align-items: center;
  gap: 0.35rem;
  color: var(--text3);
  font-family: var(--mono);
  font-size: 11px;
  white-space: nowrap;
}
.save-state svg { width: 0.85rem; height: 0.85rem; flex: none; }
.save-state[data-state="saved"] { color: var(--ok-ink); }
.save-state[data-state="idle"] svg { opacity: 0; }

.shell-btn {
  font-family: var(--font);
  font-size: 12px;
  color: var(--text2);
  background: var(--surface2);
  border: 1px solid var(--border);
  border-radius: 8px;
  padding: 5px 10px;
  cursor: pointer;
  white-space: nowrap;
  transition: all .15s;
}
.shell-btn:hover { border-color: var(--border2); color: var(--text); }

.theme-btn { display: inline-flex; align-items: center; gap: 5px; }

/* On a phone the bar wraps to three rows, and sticky at that height eats a
   sixth of the screen for the whole session. It gives up the label and the
   sticky position rather than the controls. */
@media (max-width: 600px) {
  .shell-bar { position: static; }
  .theme-btn [data-theme-label] {
    position: absolute;
    width: 1px; height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
  }
}
.shell-btn:focus-visible { outline: 2px solid var(--focus); outline-offset: 2px; }

.export-nudge {
  display: inline-flex;
  align-items: center;
  gap: 0.3rem;
  color: var(--warn-ink);
  font-family: var(--mono);
  font-size: 11px;
  white-space: nowrap;
}

/* Amber on every browser. An earlier version reserved the colour for WebKit
   and gave everyone else a grey box, which put the warning back where it
   started: looking like boilerplate. Losing work to cleared browsing data or
   a switched laptop is not a Safari-only risk, so it does not get a
   Safari-only colour. WebKit adds a sentence, not a shade. */
.storage-notice {
  display: flex;
  align-items: flex-start;
  gap: 0.6rem;
  margin: 0;
  padding: 11px 14px;
  border: 1px solid var(--warn-rule, var(--border));
  border-left: 3px solid var(--warn-icon);
  border-radius: var(--r);
  background: var(--warn-wash);
  color: var(--warn-ink);
  font-size: 12px;
  line-height: 1.65;
}
.storage-notice strong { color: var(--warn-ink); font-weight: 600; }
.storage-notice .caution-glyph { color: var(--warn-icon); }

/* Both of the above set `display`, which overrides the weaker UA rule behind
   the hidden attribute — leaving an empty frame on every browser that should
   never have seen the notice. They have to opt back out by name. */
.storage-notice[hidden],
.export-nudge[hidden] { display: none; }

.notice-inset { margin: 0.85rem clamp(1rem, 4vw, 1.5rem) 0; }

/* On the hub the notice sits in the page column, after the tool list. */
.page > .storage-notice { margin-top: 2.5rem; }

.caution-glyph { flex: none; color: var(--warn-icon); }
.storage-notice .caution-glyph { width: 1.05rem; height: 1.05rem; margin-top: 0.15rem; }
.export-nudge .caution-glyph { width: 0.85rem; height: 0.85rem; }

/* ---------- the course hub ---------- */

.page {
  max-width: 780px;
  margin: 0 auto;
  padding: clamp(2.5rem, 8vh, 4.5rem) 1.4rem 3rem;
}

/* A page carrying the bar does not also need a deep gap above its heading. */
.shell-bar + .page { padding-top: clamp(1.75rem, 5vh, 2.75rem); }

.eyebrow {
  margin: 0 0 0.5rem;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text3);
}

h1 {
  font-family: var(--display);
  font-weight: 600;
  font-size: clamp(1.4rem, 4.5vw, 1.75rem);
  letter-spacing: -.03em;
  line-height: 1.2;
  margin: 0;
  text-wrap: balance;
}

.lede {
  margin: 0.9rem 0 0;
  color: var(--text2);
  font-size: 14px;
  line-height: 1.7;
  text-wrap: pretty;
}
.lede a { color: var(--text2); text-underline-offset: 3px; text-decoration-color: var(--border2); }

.section-label {
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .07em;
  text-transform: uppercase;
  color: var(--text3);
  margin: 2.5rem 0 0.9rem;
}

.tool-list {
  list-style: none;
  margin: 0;
  padding: 0;
  display: flex;
  flex-direction: column;
  gap: 10px;
}

/* Each tool is a card with a yellow top rule, the dashboard's own card
   treatment, rather than a line of text with a glyph beside it. */
.tool-item {
  display: grid;
  grid-template-columns: 1.375rem 1fr;
  gap: 0.9rem;
  align-items: start;
  padding: 15px 17px;
  background: var(--surface);
  border: 1px solid var(--border);
  border-top: 2px solid var(--brand);
  border-radius: var(--rl);
  box-shadow: var(--shadow);
  text-decoration: none;
  color: inherit;
  transition: border-color .15s, transform .15s;
}
a.tool-item:hover { border-color: var(--border2); }
a.tool-item:focus-visible { outline: 2px solid var(--focus); outline-offset: 3px; }

.tool-item .glyph {
  width: 1.375rem;
  height: 1.375rem;
  margin-top: 0.15rem;
  color: var(--brand-ink);
}
.tool-name {
  display: block;
  font-family: var(--display);
  font-weight: 600;
  font-size: 14px;
  letter-spacing: -.01em;
  color: var(--text);
  line-height: 1.4;
}
.tool-note {
  display: block;
  font-size: 12.5px;
  color: var(--text2);
  line-height: 1.6;
  margin-top: 0.2rem;
}

/* A tool that is not open yet keeps its row so the term's shape is visible,
   but drops the yellow rule and the shadow — nothing here to click. */
li.upcoming .tool-item {
  background: none;
  border-top-color: var(--border);
  box-shadow: none;
}
li.upcoming .glyph { color: var(--text3); }
li.upcoming .tool-name { color: var(--text2); font-weight: 500; }

.when {
  display: inline-block;
  font-family: var(--mono);
  font-size: 10px;
  font-weight: 500;
  letter-spacing: .04em;
  text-transform: uppercase;
  color: var(--text2);
  background: var(--surface2);
  border-radius: 20px;
  padding: 2px 8px;
  margin-left: 0.45rem;
  vertical-align: 1px;
  white-space: nowrap;
}

.fineprint {
  margin: 2.5rem 0 0;
  padding-top: 1.4rem;
  border-top: 1px solid var(--border);
  font-size: 12px;
  color: var(--text3);
  line-height: 1.7;
}
.fineprint p { margin: 0 0 0.7rem; }
.fineprint p:last-child { margin-bottom: 0; }
.fineprint strong { color: var(--text2); font-weight: 600; }

/* ---------- dialogs used by the shell ---------- */

.sheet {
  border: 1px solid var(--border2);
  border-top: 3px solid var(--brand);
  border-radius: var(--rl);
  padding: 20px;
  max-width: 26rem;
  width: calc(100vw - 2rem);
  background: var(--surface);
  color: var(--text);
  font-family: var(--font);
  font-size: 14px;
  line-height: 1.65;
  box-shadow: 0 12px 40px rgba(0, 0, 0, .18);
}
.sheet::backdrop { background: rgba(0, 0, 0, .4); }
.sheet h2 {
  font-family: var(--display);
  font-size: 15px;
  font-weight: 600;
  letter-spacing: -.02em;
  margin: 0 0 0.4rem;
}
.sheet p { margin: 0 0 1.1rem; color: var(--text2); font-size: 13px; }
.sheet-actions { display: flex; justify-content: flex-end; gap: 0.5rem; }
.sheet-actions .shell-btn { font-size: 13px; padding: 7px 14px; }
.sheet-actions .primary {
  background: var(--brand);
  border-color: var(--brand);
  color: var(--ink-deep);
  font-weight: 500;
}
.sheet-actions .primary:hover { background: var(--brand-hover); border-color: var(--brand-hover); color: var(--ink-deep); }
.sheet-actions .danger { color: var(--bad-ink); border-color: var(--border2); background: var(--bad-wash); }
.sheet-actions .danger:hover { border-color: var(--bad-ink); color: var(--bad-ink); }

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