/* =====================================================================
   Clarence Wu — resume site
   Every value below was measured off the live pages with getComputedStyle,
   not estimated. Tokens live in :root / [data-theme]; change one there and
   it propagates everywhere.
   ===================================================================== */

/* ---------- 1. Fonts ---------------------------------------------- */
@font-face {
  font-family: 'Inter Tight';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-tight-lat.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 Tight';
  font-style: normal;
  font-weight: 100 900;
  font-display: swap;
  src: url('../fonts/inter-tight-ext.woff2') format('woff2');
  unicode-range: U+0100-02BA, U+02BD-02C5, U+02C7-02CC, U+02CE-02D7, U+02DD-02FF,
    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 {
  /* The original sets a second family on a few elements: the hero greeting,
     the English hero headline, the "Email" link and the footer year. Missing
     it is why English text read slightly off. Latin subset; CJK falls back. */
  font-family: 'Overused Grotesk Medium';
  font-style: normal;
  font-display: swap;
  src: url('../fonts/overused-grotesk-medium.woff2') format('woff2');
}

/* ---------- 2. Design tokens (measured) ---------------------------- */
:root {
  /* Warm cream ramp. The whole light scale is warm (R>G>B) — swapping only
     the background would leave the original's blue-violet greys fighting it.
     Contrast is equal to or better than the values they replace. */
  --bg:      #faf6ee;                 /* body background      (was #f5f8fd) */
  --surface: #fffdf8;                 /* marquee tiles                      */
  --ink:     #0b0a08;                 /* headings, logo, borders  18.4:1    */
  --body:    #605a53;                 /* paragraphs, hero greeting 6.3:1    */
  --muted:   #82796d;                 /* dates, contact links, year 4.0:1   */
  --nav-ink: #2b2721;                 /* nav tagline              13.8:1    */

  /* Type — sizes and line-heights exactly as measured */
  --fs-display: 89px;   --lh-display: 111.25px;  /* contact heading  */
  --fs-role:    48px;   --lh-role:    50.4px;    /* job title        */
  --fs-h2:      32px;   --lh-h2:      40px;      /* section title    */
  --fs-h3:      24px;   --lh-h3:      30px;      /* card title, hero greeting, contact links */
  --fs-body:    16px;   --lh-body:    28px;
  --fs-meta:    16px;   --lh-meta:    16.8px;    /* dates, year      */
  --fs-nav:     14px;   --lh-nav:     17.5px;

  /* Layout */
  --page:     1400px;
  --content:  1200px;
  --pad-x:    100px;
  --gap-section: 136px;

  --ease: cubic-bezier(.22, 1, .36, 1);
  color-scheme: light;
}

[data-theme="dark"] {
  --bg:      rgb(6, 6, 8);
  --surface: rgb(26, 26, 35);
  --ink:     rgb(191, 191, 198);
  --body:    rgb(200, 200, 207);
  --muted:   rgb(134, 134, 141);
  --nav-ink: rgb(200, 200, 207);
  color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) {
    --bg:      rgb(6, 6, 8);
    --surface: rgb(26, 26, 35);
    --ink:     rgb(191, 191, 198);
    --body:    rgb(200, 200, 207);
    --muted:   rgb(134, 134, 141);
    --nav-ink: rgb(200, 200, 207);
    color-scheme: dark;
  }
}


/* ---------- 2b. Second family, exactly where the original uses it ---- */
/* Verified with CDP CSS.getPlatformFontsForNode, i.e. the face Chrome really
   rasterised with — not what the CSS asked for.
   Nav links and tagline really do use Overused Grotesk. */
.nav-links a,
.nav-tagline {
  font-family: 'Overused Grotesk Medium', 'Inter Tight', sans-serif;
}

/* The greeting and the footer year ask for "Overused Grotesk Regular", a cut
   Framer never bundles, so the original falls through to sans-serif and paints
   in Arial — wider and flatter than a grotesk. Naming Arial/Helvetica directly
   reproduces what visitors actually see, instead of leaving it to chance. */
.hero-greeting,
.site-footer .year {
  font-family: Arial, Helvetica, sans-serif;
}

/* ---------- 3. Reset ---------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; scroll-padding-top: 40px; }

body {
  margin: 0;
  background: var(--bg);
  color: var(--body);
  font-family: 'Inter Tight', -apple-system, BlinkMacSystemFont, "Segoe UI",
    "PingFang TC", "Noto Sans TC", "Microsoft JhengHei", sans-serif;
  font-size: var(--fs-body);
  line-height: var(--lh-body);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
  transition: background-color .35s var(--ease), color .35s var(--ease);
}

img, svg { display: block; max-width: 100%; }
a { color: inherit; text-decoration: none; }
h1, h2, h3, h4 { margin: 0; color: var(--ink); font-weight: 500; letter-spacing: 0; }
p { margin: 0; }
ul { margin: 0; padding: 0; list-style: none; }
:focus-visible { outline: 2px solid var(--ink); outline-offset: 3px; border-radius: 4px; }

.visually-hidden {
  position: absolute; width: 1px; height: 1px; margin: -1px; padding: 0;
  overflow: hidden; clip: rect(0 0 0 0); white-space: nowrap; border: 0;
}

/* The page is one centred column with a fixed rhythm between sections. */
.page {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: var(--gap-section);
  padding-top: 48px;
  overflow: hidden;
}

/* ---------- 4. Header (in flow — it scrolls away) ------------------ */
.site-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: var(--page);
  max-width: 100%;
  padding-inline: var(--pad-x);
}

.nav-avatar {
  width: 50px; height: 50px;
  flex: none;
  overflow: hidden;
  border-radius: 50%;
}
.nav-avatar img { width: 100%; height: 100%; object-fit: cover; }

.brand { display: inline-flex; align-items: center; gap: 10px; }
.brand .mark { width: 29px; height: 4px; flex: none; overflow: visible; }
.brand .mark line { stroke: var(--ink); stroke-width: 2; }
.brand .name {
  width: 100px;              /* measured; keeps the logo group at 139px */
  font-size: var(--fs-nav); line-height: var(--lh-nav);
  font-weight: 600; color: var(--ink);
}

/* Tagline and links travel together as one flex child, 154px apart. */
.nav-mid {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 154px;
}

.nav-tagline {
  display: flex; flex-direction: column;
  font-size: var(--fs-nav); line-height: var(--lh-nav);
  color: var(--nav-ink);
}

.nav-links { display: flex; align-items: flex-start; gap: 32px; }
.nav-links a {
  position: relative;
  font-size: var(--fs-nav); line-height: var(--lh-nav);
  color: var(--ink);
}
.nav-links a { transition: color .3s var(--ease); }
.nav-links a:hover { color: var(--muted); }

.nav-end { display: flex; align-items: center; gap: 20px; }

.lang-switch {
  display: inline-flex; align-items: center; gap: 6px;
  font-size: var(--fs-nav); line-height: var(--lh-nav); color: var(--muted);
}
.lang-switch a[aria-current="true"] { color: var(--ink); font-weight: 600; }

.theme-toggle {
  display: grid; place-items: center;
  /* Hit area stays 24x24 so the header spacing is unchanged; only the
     circular plate is gone, leaving the bare moon glyph. */
  width: 24px; height: 24px;
  flex: none; padding: 0;
  border: 0;
  background: none;
  cursor: pointer;
  transition: transform .25s var(--ease), opacity .25s var(--ease);
}
.theme-toggle:hover { transform: translateY(-1px); opacity: .65; }
.theme-toggle svg { width: 16px; height: 16px; fill: var(--ink); transition: transform .5s var(--ease); }
/* Moon in light mode (click -> dark); sun in dark mode (click -> light). */
.theme-toggle .icon-sun { display: none; }
.theme-toggle .icon-sun circle { fill: var(--ink); }
.theme-toggle .icon-sun g path { fill: none; stroke: var(--ink); stroke-width: 1.3; stroke-linecap: round; }
[data-theme="dark"] .theme-toggle .icon-moon { display: none; }
[data-theme="dark"] .theme-toggle .icon-sun  { display: block; }
@media (prefers-color-scheme: dark) {
  :root:not([data-theme="light"]) .theme-toggle .icon-moon { display: none; }
  :root:not([data-theme="light"]) .theme-toggle .icon-sun  { display: block; }
}

.nav-toggle { display: none; }

/* ---------- 5. Buttons (139×48 / 96×48, radius 24, 1.5px) ---------- */
.btn {
  position: relative; isolation: isolate;
  display: inline-flex; align-items: center; justify-content: center;
  gap: 10px;
  height: 48px; padding: 10px 18px;
  flex: none;
  overflow: hidden;
  border: 1.5px solid var(--ink);
  border-radius: 24px;
  background: transparent;
  color: var(--ink);
  font-size: var(--fs-body); line-height: 1;
  cursor: pointer;
  transition: color .4s var(--ease);
}
.btn::before {
  content: "";
  position: absolute; inset: 0; z-index: -1;
  background: var(--ink);
  transform: translateY(101%);
  transition: transform .45s var(--ease);
}
.btn:hover, .btn:focus-visible { color: var(--bg); }
.btn:hover::before, .btn:focus-visible::before { transform: translateY(0); }
.btn .arrow { width: 16px; height: 16px; flex: none; transition: transform .45s var(--ease); }
.btn .arrow path { fill: currentColor; }
.btn:hover .arrow { transform: translateX(4px); }

/* ---------- 6. Hero ------------------------------------------------ */
.hero {
  display: flex; flex-direction: column; align-items: center;
  gap: 48px;
  width: 100%;
  padding: 56px var(--pad-x) 0;
}
.hero-inner {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 48px;
  width: 100%; max-width: var(--content);
}
.hero-text { display: flex; flex-direction: column; align-items: flex-start; gap: 32px; }
.hero-lines { display: flex; flex-direction: column; align-items: flex-start; gap: 10px; }
.hero-greeting { font-size: var(--fs-h3); line-height: var(--lh-h3); color: var(--body); }
.hero h1 { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: 500; }
/* The English hero has a third tier: a 32px lead, then a 89px display line. */
.hero-lead { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: 500; color: var(--ink); }
.hero h1.is-display { font-size: var(--fs-display); line-height: var(--lh-display); }

/* Marquee: 74×72 tiles, 68px gap, track duplicated so it loops seamlessly. */
.marquee {
  /* 72px total, same as the original: the tiles are 72px tall and overflow
     the 10px padding rather than adding to it. Padding it the naive way made
     the strip 92px and pushed every section below down by 20px. */
  display: flex;
  align-items: center;
  height: 72px;
  width: 100%; max-width: var(--content);
  padding: 0 10px;
  overflow: hidden;
  -webkit-mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
          mask-image: linear-gradient(90deg, transparent, #000 6%, #000 94%, transparent);
}
/* The track is driven by site.js, not a CSS animation: hovering eases the
   speed down to a tenth (measured 100 px/s -> 10 px/s) rather than stopping,
   and a CSS animation cannot change rate without jumping position. */
.marquee-track {
  display: flex;
  align-items: center;
  gap: 68px;
  width: max-content;
  will-change: transform;
}

.tool {
  display: flex; align-items: center; justify-content: center;
  width: 74px; height: 72px;
  flex: none;
  /* No padding: the icon is 40x40 and the tile is 74x72, so a 17/21 padding
     box (32x38) would be smaller than the icon and it could not centre. */
  padding: 0;
  border-radius: 19px;
  background: var(--surface);
  overflow: hidden;
}
.tool img, .tool svg { width: 40px; height: 40px; flex: none; }
/* cover, not contain: the artwork must fill the box or the rounded corners
   have nothing to clip and the icon still reads as a square. Each icon's
   exact radius and size are measured per file and set inline in the HTML. */
.tool img { object-fit: cover; border-radius: 10px; }
.tool svg { object-fit: contain; border-radius: 0; }

/* ---------- 7. Section header (index and title side by side) ------- */
.section {
  display: flex; flex-direction: column; align-items: center;
  width: var(--page); max-width: 100%;
  padding-inline: var(--pad-x);
  scroll-margin-top: 24px;
}
.section-projects { gap: 24px; }
.section-experience { gap: 48px; }

.section-head {
  display: flex; flex-direction: row; align-items: flex-start;
  gap: 16px;
  width: 100%; max-width: var(--content);
}
.section-head .index {
  flex: none;
  font-size: var(--fs-meta); line-height: 20px;
  color: rgb(92, 92, 105);
}
.section-head h2 { font-size: var(--fs-h2); line-height: var(--lh-h2); font-weight: 500; }

/* ---------- 8. Competency cards ------------------------------------ */
.card-grid {
  display: grid;
  grid-template-columns: 586px 586px;
  justify-content: center;
  gap: 28px;
  width: 100%; max-width: var(--content);
}
.card {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 28px;
  padding: 5px;
  width: 100%;
}
.card-media {
  width: 100%;
  aspect-ratio: 1.05882 / 1;
  overflow: hidden;
  border-radius: 50px;
}
.card-media img { width: 100%; height: 100%; object-fit: cover; }

.card-body {
  display: flex; flex-direction: row;
  align-items: center; justify-content: space-between;
  width: 100%;
}
.card-title { display: flex; flex-direction: column; align-items: flex-start; gap: 4px; }
.card-title h3 { font-size: var(--fs-h3); line-height: var(--lh-h3); font-weight: 500; }
.card-title p { font-size: var(--fs-body); line-height: var(--lh-body); color: var(--body); }

.card-arrow {
  display: flex; align-items: center; justify-content: center;
  width: 62px; height: 50px;
  flex: none;
  border: 1.5px solid var(--ink);
  border-radius: 100px;
}
.card-arrow svg { width: 20px; height: 20px; }
.card-arrow svg path { fill: var(--ink); }

/* ---------- 9. Experience (flat — no card, no shadow) -------------- */
.timeline {
  display: flex; flex-direction: column; align-items: center;
  gap: 64px;
  width: 100%; max-width: var(--content);
}
.job {
  display: flex; flex-direction: row; align-items: center;
  gap: 40px;
  width: 100%;
}
.job-main { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; flex: 1 1 auto; min-width: 0; }

.job-head { display: flex; flex-direction: row; align-items: flex-end; gap: 10px; width: 100%; }
/* Rounded plate. object-fit stays `contain`: the Genesis mark is 140x65, so
   `cover` would crop the wordmark. Square opaque logos fill the box and pick
   up the rounding; letterboxed wordmarks simply sit inside it. */
.job-logo {
  width: 50px; height: 50px;
  flex: none;
  overflow: hidden;
  border-radius: 10px;
}
.job-logo img { width: 100%; height: 100%; object-fit: contain; border-radius: inherit; }

.job-headings { display: flex; flex-direction: column; align-items: flex-start; gap: 16px; min-width: 0; }
.job-date {
  font-size: var(--fs-meta); line-height: var(--lh-meta); font-weight: 400;
  text-transform: uppercase;
  color: var(--muted);
}
.job-role { font-size: var(--fs-role); line-height: var(--lh-role); font-weight: 400; color: var(--ink); }

.job-body { display: flex; flex-direction: column; width: 100%; }
.job-body p, .job-body li { font-size: var(--fs-body); line-height: var(--lh-body); color: var(--body); }
.job-body .group-label { color: var(--body); }
.job-cta { flex: none; }

/* ---------- 10. Contact -------------------------------------------- */
.contact {
  display: flex; flex-direction: column; align-items: flex-start;
  gap: 64px;
  width: var(--page); max-width: 100%;
  padding: 0 var(--pad-x) 56px;
}
.contact-inner { display: flex; flex-direction: column; align-items: flex-start; gap: 32px; width: 100%; max-width: var(--content); }

.contact-head { display: flex; flex-direction: row; align-items: center; gap: 48px; width: 100%; }
.contact-head h2 {
  font-size: var(--fs-display); line-height: var(--lh-display);
  font-weight: 500;
  /* Let it take the space the portrait and circle leave (1200 - 264 - 264 -
     2x48 = 576px, exactly the original) and wrap naturally. `flex: none`
     let it run to 821px, so the English heading never wrapped correctly. */
  flex: 1 1 auto;
  min-width: 0;
}

.contact-portrait {
  width: 264px; height: 264px;
  flex: none;
  overflow: hidden;
  border-radius: 50%;
}
.contact-portrait img { width: 100%; height: 100%; object-fit: cover; }

/* The outlined circle with the diagonal arrow, beside the portrait. */
.contact-circle {
  display: grid; place-items: center;
  width: 264px; height: 264px;
  flex: none;
  border: 1.5px solid var(--ink);
  border-radius: 50%;
  /* Measured: rocks between ~0deg and ~26deg, dwelling at both ends,
     full period ~2.87s. Not scroll-linked - it runs on its own clock. */
  animation: rock 1.435s cubic-bezier(.44, 0, .56, 1) infinite alternate;
  transition: background .5s var(--ease);
}
@keyframes rock {
  from { transform: rotate(0deg); }
  to   { transform: rotate(26deg); }
}
.contact-circle svg { width: 88px; height: 88px; }
.contact-circle svg path { fill: none; stroke: var(--ink); stroke-width: 1.5; transition: stroke .4s var(--ease); }
.contact-circle:hover { background: var(--ink); }
.contact-circle:hover svg path { stroke: var(--bg); }

.contact-note { font-size: var(--fs-body); line-height: var(--lh-body); color: var(--body); max-width: 900px; }

.contact-links { display: flex; flex-direction: row; align-items: flex-start; gap: 34px; }
.contact-links a {
  position: relative;
  font-size: var(--fs-h3); line-height: 25.2px;
  color: var(--muted);
  transition: color .3s var(--ease);
}
.contact-links a:hover { color: var(--ink); }

/* ---------- 11. Footer --------------------------------------------- */
.site-footer {
  display: flex; flex-direction: row; align-items: center;
  gap: 48px;
  width: 100%; max-width: var(--content);
}
.site-footer .year { font-size: var(--fs-meta); line-height: var(--lh-meta); color: var(--muted); }

/* ---------- 12. Scroll reveal -------------------------------------- */
/* Measured off the original: 700ms, symmetric ease. Competency cards fade
   with no movement; experience rows also rise 150px. Section headers, the
   contact block and the footer are NOT animated on the original at all. */
[data-reveal] {
  opacity: 0;
  transition: opacity .53s cubic-bezier(.44, 0, .56, 1),
              transform .53s cubic-bezier(.44, 0, .56, 1);
  transition-delay: var(--reveal-delay, 0ms);
}
[data-reveal="rise"] { transform: translateY(150px); }
[data-reveal].is-visible { opacity: 1; transform: none; }

/* ---------- 13. Load fades (measured on the original) ----------------
   Header, hero and marquee are above the fold, so they fade on load
   rather than on scroll. Durations are the measured settle times.      */
@keyframes fade-in { from { opacity: 0; } to { opacity: 1; } }

.site-header { animation: fade-in 1.04s cubic-bezier(.44, 0, .56, 1) .3s both; }
.marquee     { animation: fade-in  .42s cubic-bezier(.44, 0, .56, 1) both; }

/* The hero headline block is masked and rises into place: it sits 100px low
   and hidden, waits ~0.5s, then travels up over ~0.52s with an ease-out.
   The greeting rises a further 30px on a long ~2s tail. The Contact Me
   button is NOT animated on the original - it is solid from the first paint. */
@keyframes hero-rise  { from { opacity: 0; transform: translateY(100px); }
                        to   { opacity: 1; transform: none; } }
@keyframes greet-rise { from { opacity: 0; transform: translateY(30px); }
                        to   { opacity: 1; transform: none; } }

.hero-lines    { overflow: hidden; animation: hero-rise .52s cubic-bezier(.5, 1, .89, 1) .26s both; }
.hero-greeting { animation: greet-rise 2.6s cubic-bezier(.16, 1, .3, 1) both; }

/* ---------- 14. Responsive ----------------------------------------- */
@media (max-width: 1200px) {
  :root { --pad-x: 40px; --fs-display: 56px; --lh-display: 70px; --fs-role: 32px; --lh-role: 40px; --gap-section: 96px; }
  .page { padding-top: 32px; }
  .card-grid { grid-template-columns: minmax(0, 1fr); }
  .nav-tagline { display: none; }
  .contact-circle { display: none; }
  .job { flex-direction: column; align-items: flex-start; gap: 24px; }
  .job-cta { align-self: flex-start; }
}

@media (max-width: 810px) {
  :root {
    --pad-x: 20px; --fs-display: 44px; --lh-display: 55px;
    --fs-h2: 28px; --lh-h2: 35px; --fs-role: 26px; --lh-role: 34px;
    --fs-h3: 20px; --lh-h3: 26px; --gap-section: 72px;
  }
  /* The header needs its own stacking level: the hero block is transformed
     by its entrance animation, which makes it a stacking context of its own,
     and the panel's z-index could not beat it while the header was z:auto.
     That is why menu items showed through the hero text. */
  .site-header { position: relative; z-index: 50; }
  .contact-head { flex-direction: column-reverse; align-items: flex-start; gap: 28px; }
  .contact-portrait { width: 160px; height: 160px; }
  .contact-links { flex-wrap: wrap; gap: 20px; }
  .site-footer { gap: 24px; }
  .job-head { align-items: center; }

  /* The toggle is ordered last so it sits hard against the right edge,
     where a menu button is expected — it used to sit mid-row with the
     language and theme controls to its right. The panel is a solid
     full-width sheet with divided rows, so it reads as a menu instead of
     floating text over the hero. */
  .nav-end    { order: 90; }
  .nav-toggle { order: 99; }

  .nav-mid .nav-links {
    position: absolute;
    left: 0; right: 0; top: 100%;
    flex-direction: column;
    align-items: stretch;
    gap: 0;
    margin-top: 14px;
    padding: 4px var(--pad-x) 12px;
    background: var(--bg);
    border-top: 1px solid var(--line);
    box-shadow: 0 20px 34px -20px rgba(6, 6, 8, .45);
    transform: translateY(-10px);
    opacity: 0;
    pointer-events: none;
    transition: opacity .28s var(--ease), transform .28s var(--ease);
    z-index: 30;
  }
  .site-header.is-open .nav-links { opacity: 1; transform: none; pointer-events: auto; }

  .nav-mid .nav-links a {
    padding: 15px 2px;
    font-size: 18px;
    border-bottom: 1px solid var(--line);
  }
  .nav-mid .nav-links a:last-child { border-bottom: 0; }

  /* Three bars morphing into a real X (two bars only ever made a ">"). */
  .nav-toggle {
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    gap: 5px;
    width: 40px; height: 40px;
    padding: 0; border: 0;
    background: none;
    cursor: pointer;
    z-index: 40;
  }
  .nav-toggle span {
    display: block;
    width: 20px; height: 1.5px;
    background: var(--ink);
    transition: transform .3s var(--ease), opacity .2s var(--ease);
  }
  .site-header.is-open .nav-toggle span:nth-child(1) { transform: translateY(6.5px) rotate(45deg); }
  .site-header.is-open .nav-toggle span:nth-child(2) { opacity: 0; }
  .site-header.is-open .nav-toggle span:nth-child(3) { transform: translateY(-6.5px) rotate(-45deg); }
}

@media (prefers-reduced-motion: reduce) {
  html { scroll-behavior: auto; }
  .marquee-track, .contact-circle { animation: none; }
  .site-header, .hero-lines, .hero-greeting, .marquee { animation: none; opacity: 1; transform: none; }
  [data-reveal] { opacity: 1; transform: none; transition: none; }
}

@media print {
  .site-header, .marquee, .theme-toggle, .contact-circle { display: none !important; }
  body { background: #fff; color: #000; }
  .page { gap: 32px; }
}
