/* ------------------------------------------------------------------
   site.css — runtime layer reproducing the behaviour Framer's React
   bundle applies at runtime (scroll reveals, hover variants).
   Values measured from the live site; see README.md.
   ------------------------------------------------------------------ */

/* --- Scroll reveal -------------------------------------------------
   Measured on timkerger.com: opacity 0 -> 1 over ~1000ms, no transform,
   no stagger, triggered the moment the element's top edge crosses the
   viewport bottom. Easing fitted to the sampled curve (max err 0.008). */
[data-reveal] {
  opacity: 0;
  transition: opacity 1000ms cubic-bezier(0.29, 0.49, 0.31, 1);
}
[data-reveal].framer-revealed {
  opacity: 1;
}
@media (prefers-reduced-motion: reduce) {
  [data-reveal] { opacity: 1; transition: none; }
}
/* If scripting is unavailable, never leave content invisible. */
.no-js [data-reveal] { opacity: 1; transition: none; }

/* --- Hover states --------------------------------------------------
   Framer toggles a `.hover` class on components marked data-highlight.
   Measured effect: muted token (#595959) -> primary token (#0a0a0a)
   on both the icon glyph and its label. */
.framer-qN7cB svg, .framer-qN7cB svg *,
.framer-XdPwZ svg, .framer-XdPwZ svg *,
.framer-smKSZ svg, .framer-smKSZ svg *,
.framer-AX5Cq svg, .framer-AX5Cq svg *,
.framer-0knC4 svg, .framer-0knC4 svg *,
.framer-8Rotp svg, .framer-8Rotp svg * {
  transition: color 200ms ease, fill 200ms ease;
}
.framer-qN7cB p.framer-text,
.framer-XdPwZ p.framer-text,
.framer-smKSZ p.framer-text,
.framer-AX5Cq p.framer-text {
  transition: color 200ms ease;
}

.framer-qN7cB.hover svg, .framer-qN7cB.hover svg *,
.framer-XdPwZ.hover svg, .framer-XdPwZ.hover svg *,
.framer-smKSZ.hover svg, .framer-smKSZ.hover svg *,
.framer-AX5Cq.hover svg, .framer-AX5Cq.hover svg *,
.framer-0knC4.hover svg, .framer-0knC4.hover svg * {
  color: var(--token-dc11ae94-e5e0-46b4-a7bb-6a89927d0b69, #0a0a0a) !important;
  fill: var(--token-dc11ae94-e5e0-46b4-a7bb-6a89927d0b69, #0a0a0a) !important;
}
.framer-qN7cB.hover p.framer-text,
.framer-XdPwZ.hover p.framer-text,
.framer-smKSZ.hover p.framer-text,
.framer-AX5Cq.hover p.framer-text {
  --framer-text-color: var(--token-dc11ae94-e5e0-46b4-a7bb-6a89927d0b69, #0a0a0a) !important;
  color: var(--token-dc11ae94-e5e0-46b4-a7bb-6a89927d0b69, #0a0a0a) !important;
}

/* Project card: thumbnail expands (inset rule ships in Framer's own CSS)
   and the corner arrow un-rotates from 45deg to 0 over ~310ms. */
.framer-0knC4 .framer-14f93cj {
  transition: inset 310ms cubic-bezier(0.29, 0.49, 0.31, 1);
}
.framer-0knC4 .framer-1g0wwhj-container {
  transition: transform 310ms cubic-bezier(0.29, 0.49, 0.31, 1);
}
.framer-0knC4.hover .framer-1g0wwhj-container {
  transform: none !important;
}
/* The tablet/phone card variant gets the same thumbnail growth. */
.framer-0knC4.framer-v-1wjukaz.hover .framer-14f93cj {
  inset: -6px -8px;
}

/* --- Copy-to-clipboard button -------------------------------------
   The overlay button cross-fades its label to "Copied!" for ~2s. */
.framer-165nml1-container,
.framer-copy-overlay {
  transition: opacity 200ms ease;
}
[data-copy-label] > span,
[data-copy-done] {
  transition: opacity 200ms ease;
}

/* --- Phone-only hero text alignment -------------------------------
   On the phone breakpoint Framer re-renders the hero heading pair with
   --framer-text-alignment: center. Framer's own preset rule sets that
   variable to `start` at a higher specificity, and on the live site an
   inline style is what overrides it — hence !important here. */
@media (max-width: 809.98px) {
  .framer-zp8o9z .framer-styles-preset-cnkitt,
  .framer-zp8o9z .framer-styles-preset-c9pypk {
    --framer-text-alignment: center !important;
  }
}

/* --- Submit button ------------------------------------------------
   Measured on the live site: the filled button lightens from the
   primary token (#0a0a0a) to the muted token (#595959) on hover.
   The base colour is an inline style, hence !important. */
.framer-8Rotp {
  transition: background-color 200ms ease;
}
.framer-8Rotp.hover {
  background-color: var(--token-66a88aa6-8fc9-447d-b229-c1bafaea57af, #595959) !important;
}

/* --- Embedded interactive diagram (2026 State Budget) --------------
   The budget export is a complete document with its own stylesheet,
   theme handling and scripts, so it is embedded rather than inlined.
   On desktop it breaks out of the 700px reading column, which the
   diagram needs to stay legible. */
.tk-embed {
  /* the reading column already carries the page gutter */
  margin: 28px auto 4px;
  width: 100%;
}
.tk-embed__frame {
  position: relative;
  width: 100%;
  height: 540px;
  overflow: hidden;
  border-radius: 8px;
  border: 1px solid var(--token-a5aa483f-4f79-4648-a71d-783f09603577, #ababab);
  background: var(--token-a65aeb92-d759-4a39-b772-8775ba6f096c, #fff);
}
.tk-embed__frame iframe {
  display: block;
  width: 100%;
  height: 100%;
  border: 0;
}
.tk-embed__caption {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  gap: 4px 12px;
  margin: 10px 0 0;
  font-family: "Inter", "Inter Placeholder", sans-serif;
  font-size: 14px;
  line-height: 1.4em;
  color: var(--token-66a88aa6-8fc9-447d-b229-c1bafaea57af, #595959);
}
.tk-embed__open {
  color: var(--token-dc11ae94-e5e0-46b4-a7bb-6a89927d0b69, #0a0a0a);
  text-decoration: none;
  border-bottom: 1px solid currentColor;
  padding-bottom: 1px;
  white-space: nowrap;
}
.tk-embed__open::after { content: " \2197"; }
.tk-embed__open:hover { opacity: .65; }

@media (min-width: 810px) {
  .tk-embed__frame { height: 620px; }
}
@media (min-width: 1200px) {
  /* The diagram needs more than the 700px reading column. Scoped to this page
     only, so the cloned pages keep Framer's clipping exactly as it was. */
  [data-tk-page="budget"] .framer-1riu3aw,
  [data-tk-page="budget"] .framer-1tdf5yw { overflow: visible; }
  .tk-embed {
    width: min(1160px, calc(100vw - 80px));
    max-width: none;
    margin-left: 50%;
    transform: translateX(-50%);
  }
  .tk-embed__frame { height: 760px; }
}

/* --- Sources list (2026 State Budget) ------------------------------
   The law the dataset is parsed from, and the dataset itself. */
.tk-sources {
  margin: 26px 0 4px;
  width: 100%;
  font-family: "Inter", "Inter Placeholder", sans-serif;
}
.tk-sources__h {
  margin: 0 0 10px;
  font-size: 14px;
  font-weight: 500;
  letter-spacing: 0.04em;
  text-transform: uppercase;
  color: var(--token-66a88aa6-8fc9-447d-b229-c1bafaea57af, #595959);
}
.tk-sources__list {
  list-style: none;
  margin: 0;
  padding: 0;
  border-top: 1px solid var(--token-daafa54d-7b22-4a97-bd53-87b12775dffd, #ebebeb);
}
.tk-sources__list li {
  display: flex;
  flex-wrap: wrap;
  align-items: baseline;
  justify-content: space-between;
  gap: 4px 16px;
  padding: 11px 0;
  border-bottom: 1px solid var(--token-daafa54d-7b22-4a97-bd53-87b12775dffd, #ebebeb);
  font-size: 14px;
  line-height: 1.4em;
}
.tk-sources__list a {
  color: var(--token-dc11ae94-e5e0-46b4-a7bb-6a89927d0b69, #0a0a0a);
  text-decoration: none;
  border-bottom: 1px solid transparent;
  transition: border-color 200ms ease;
}
.tk-sources__list a:hover {
  border-bottom-color: currentColor;
}
.tk-sources__list span {
  color: var(--token-66a88aa6-8fc9-447d-b229-c1bafaea57af, #595959);
  font-variant-numeric: tabular-nums;
  white-space: nowrap;
}

/* ==================================================================
   HIDDEN: "Download CV" button (homepage hero)
   ------------------------------------------------------------------
   Temporarily hidden on request. The markup and the PDF are both still
   in place — nothing was deleted — so restoring it is a one-line edit.

   TO BRING IT BACK: delete this block (down to the closing brace).

   Why this selector: .framer-ackhvs is the "Links" wrapper and it holds
   the Download CV control and nothing else, so hiding the wrapper also
   removes its 16px flex gap. Hiding the inner .framer-1pax6k8-container
   instead would leave an empty 32px gap on phones.

   display:none also takes it out of the tab order and the accessibility
   tree, which is what we want for a control that isn't offered.
   ================================================================== */
.framer-ackhvs {
  display: none !important;
}

/* --- Copy-to-clipboard confirmation --------------------------------
   Framer's own "Copied!" label sits inside an overlay that never leaves
   opacity 0, so copying the address produced no feedback at all.

   Rather than adding anything beside the address, the copy glyph itself
   becomes a tick and settles back after ~1.5s. It inherits currentColor,
   so it stays monochrome with the rest of the site and follows both the
   hover state and dark mode without any extra rules. */
.tk-copy .framer-1c9z9ab-container {
  position: relative;
}

/* the two glyphs share the same 16px slot and cross-fade in place */
.tk-copy .framer-1c9z9ab-container > div > svg,
.tk-check {
  transition: opacity 200ms ease, transform 260ms cubic-bezier(0.2, 0.7, 0.3, 1);
}
.tk-check {
  position: absolute;
  inset: 0;
  width: 100%;
  height: 100%;
  opacity: 0;
  transform: scale(0.7);
  pointer-events: none;
  overflow: visible;
  /* match the copy glyph at rest; the hover rule above then darkens both
     together, since the stroke is painted with currentColor */
  color: var(--token-66a88aa6-8fc9-447d-b229-c1bafaea57af, #595959);
}

/* The hover rule above forces `fill` onto every svg descendant, which filled
   the tick's open stroke into a solid blob. The tick is stroke-only, and these
   selectors outrank it (0,3,1 vs 0,3,0). */
.framer-qN7cB.hover .tk-check,
.framer-qN7cB.hover .tk-check path,
.tk-check,
.tk-check path {
  fill: none !important;
}
.tk-check path {
  stroke-dasharray: 26;
  stroke-dashoffset: 26;
}

.tk-copy.is-copied .framer-1c9z9ab-container > div > svg {
  opacity: 0;
  transform: scale(0.7);
}
.tk-copy.is-copied .tk-check {
  opacity: 1;
  transform: scale(1);
}
.tk-copy.is-copied .tk-check path {
  animation: tk-draw 300ms 70ms cubic-bezier(0.65, 0, 0.35, 1) forwards;
}
@keyframes tk-draw { to { stroke-dashoffset: 0; } }

/* the polite announcement, for screen readers only */
.tk-sr {
  position: absolute;
  width: 1px; height: 1px;
  margin: -1px; padding: 0; border: 0;
  overflow: hidden; white-space: nowrap;
  clip: rect(0 0 0 0);
  clip-path: inset(50%);
}

@media (prefers-reduced-motion: reduce) {
  .tk-copy .framer-1c9z9ab-container > div > svg,
  .tk-check { transition: opacity 120ms linear; transform: none; }
  .tk-copy.is-copied .framer-1c9z9ab-container > div > svg { transform: none; }
  .tk-copy.is-copied .tk-check { transform: none; }
  .tk-copy.is-copied .tk-check path { animation: none; stroke-dashoffset: 0; }
}
