/* ==========================================================================
   privacy.css — the privacy policy as part of the site, not an annex to it.

   THE IDEA: this is the one page whose subject matter is redaction, so it
   arrives redacted and declassifies itself. Every body paragraph starts under
   a solid emerald bar; the bars retract to the right in sequence and leave
   the type behind. The joke is the point — the bars lift to show there was
   never anything under them worth hiding.

   Deliberately NOT one of the site's other three signatures. The scroll
   settles, the cube solves in 3D, the cursor tracks and locks on. This drags
   a hard edge across type. Same vocabulary — pixel blocks, emerald, no soft
   fades — a different verb.

   It needs no JavaScript: the whole sequence is CSS animation, and
   `forwards` leaves every bar at scaleX(0), so with scripting blocked or
   animation unsupported the page is simply readable. Nothing here can end up
   hiding the policy.

   Everything is on ONE page and, on a desktop, in ONE screen: the three
   clauses sit side by side instead of stacking into a scroll.
   ========================================================================== */

:root {
  --bg-0: #030d0b;
  --bg-1: #06181490;
  --bg-2: #0a2620;
  --line: #1d4a3c;
  --line-soft: #12332a;
  --emerald: #35b98a;
  --emerald-deep: #186048;
  --teal: #3fbfb0;
  --foam: #dcf2e8;
  --mist: #8fb8a8;
  --stripe: #e8873a;
  --font-body: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif;
  --font-pixel: "Silkscreen", "Press Start 2P", ui-monospace, "SF Mono", Menlo, monospace;
}

* { box-sizing: border-box; margin: 0; padding: 0; }

/* ==========================================================================
   T4 — THE PUSH (site -> privacy)

   A slide deck advancing: the site leaves to the LEFT while privacy arrives
   from the RIGHT, both moving together at the same speed, edge to edge, with
   no gap between them and no cross-fade. That lockstep is the whole thing —
   it is what makes it read as one strip of paper being pulled across rather
   than as two separate animations that happen to overlap. Same duration,
   same easing, no delay on either half.

   These rules live HERE, in the destination's stylesheet, because for a
   cross-document transition the entire ::view-transition pseudo tree belongs
   to the INCOMING document. privacy.css therefore drives both halves of the
   journey out, and site.css drives both halves of the journey back.

   Why this beats the portal shutter it has to be measured against: the
   shutter has to spend a beat on a dark screen between the two pages,
   because a page crushed to a line has nowhere to be. A push never shows
   nothing — every frame is 100% page, and the two are adjacent, so the eye
   is carried across instead of being interrupted and then re-established.
   Opacity is never touched here either, for the same reason it is not
   touched there.

   Easing is a true in-out (.65,0,.25,1): a slide that starts and stops dead
   reads mechanical, and one that only decelerates reads like it was dropped.
   ========================================================================== */
::view-transition-old(root),
::view-transition-new(root) { mix-blend-mode: normal; }
::view-transition-old(root) { animation: push-out-left 500ms cubic-bezier(.65, 0, .25, 1) both; }
::view-transition-new(root) { animation: push-in-right 500ms cubic-bezier(.65, 0, .25, 1) both; }
@keyframes push-out-left { from { transform: translateX(0);     } to { transform: translateX(-100%); } }
@keyframes push-in-right { from { transform: translateX(100%);  } to { transform: translateX(0);     } }

/* The brand mark is the one thing that does NOT travel. Both documents put it
   in the same place, so naming it holds it still while the pages slide past
   underneath — the chrome stays put and the content moves, which is what a
   deck advancing inside a frame actually looks like. Its duration is pulled
   down to match the push; site.css sets 720ms for the portal morph, which
   here would leave the logo still settling long after the slide had landed. */
::view-transition-group(vt-logo) {
  animation-duration: 500ms;
  animation-timing-function: cubic-bezier(.65, 0, .25, 1);
}
::view-transition-old(vt-logo), ::view-transition-new(vt-logo) {
  animation-duration: 140ms;
  animation-timing-function: linear;
}
::view-transition-group(root)    { z-index: 0; }
::view-transition-group(vt-logo) { z-index: 1; }

@media (prefers-reduced-motion: reduce) {
  ::view-transition-group(*),
  ::view-transition-old(*),
  ::view-transition-new(*) { animation: none !important; }
}

/* No background on html — body's propagates to the canvas, and giving html
   its own would bury .bg-bleed. Same rule as site.css, same reason. */
html { height: 100%; }
body {
  min-height: 100%;
  background: var(--bg-0);
  color: var(--foam);
  font-family: var(--font-body);
  line-height: 1.65;
}
::selection { background: var(--emerald-deep); color: var(--foam); }

/* ---- the site's own backdrop ---- */
.bg-bleed {
  position: fixed;
  inset: 0;
  z-index: -2;
  background-image: url("../assets/bg-wall.jpg");
  background-size: cover;
  background-position: center;
}
.bg-bleed::after {
  content: "";
  position: absolute;
  inset: 0;
  background: linear-gradient(180deg, rgba(3, 13, 11, .78) 0%, rgba(3, 13, 11, .92) 50%, rgba(3, 13, 11, .96) 100%);
}
.bg-scan {
  position: fixed;
  inset: 0;
  z-index: -1;
  pointer-events: none;
  background-image: repeating-linear-gradient(0deg, rgba(255, 255, 255, .015) 0 1px, transparent 1px 3px);
}

/* ---- nav, matching the public site ---- */
.nav {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 16px clamp(20px, 5vw, 56px);
  background: rgba(4, 16, 13, .8);
  backdrop-filter: blur(12px);
  border-bottom: 1px solid var(--line-soft);
}
.nav-logo {
  font-family: var(--font-pixel);
  font-size: 15px;
  color: var(--foam);
  text-decoration: none;
  letter-spacing: .06em;
  view-transition-name: vt-logo;   /* the fixed point across the transition */
}
.nav-logo b { color: var(--emerald); font-weight: inherit; }
.btn {
  display: inline-block;
  font-family: var(--font-pixel);
  font-size: 12px;
  letter-spacing: .08em;
  text-transform: uppercase;
  text-decoration: none;
  color: var(--foam);
  background: transparent;
  border: 1px solid var(--line);
  padding: 10px 18px;
  cursor: pointer;
  transition: border-color .25s, background .25s, box-shadow .25s, color .25s,
              transform .2s cubic-bezier(.16, 1, .3, 1);
}
.btn:hover {
  border-color: var(--emerald);
  box-shadow: 0 0 0 1px var(--emerald), 0 0 18px rgba(53, 185, 138, .25);
}
.btn:active { transform: translateY(1px) scale(.985); transition: transform .04s linear; }

/* ---- layout: one page, and on a desktop one screen ---- */
.privacy {
  min-height: 100svh;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: clamp(92px, 12vh, 132px) clamp(20px, 5vw, 56px) clamp(32px, 6vh, 56px);
}
.privacy-inner { width: min(1180px, 100%); }

.p-head {
  display: grid;
  grid-template-columns: 1.35fr .65fr;
  gap: clamp(24px, 4vw, 56px);
  align-items: end;
  padding-bottom: clamp(16px, 2.4vh, 26px);
  border-bottom: 1px solid var(--line-soft);
}
.kicker {
  display: flex;
  align-items: center;
  gap: 10px;
  font-family: var(--font-pixel);
  font-size: 11px;
  letter-spacing: .18em;
  text-transform: uppercase;
  color: var(--emerald);
  margin-bottom: 14px;
}
.gem {
  width: 12px;
  height: 12px;
  background: linear-gradient(135deg, var(--teal), var(--emerald) 55%, var(--emerald-deep));
  box-shadow: 0 0 10px rgba(63, 191, 176, .5);
  animation: gemspin 6s linear infinite;
}
@keyframes gemspin {
  0%   { transform: rotate(45deg) scale(1); }
  50%  { transform: rotate(225deg) scale(.82); }
  100% { transform: rotate(405deg) scale(1); }
}

h1 {
  font-weight: 650;
  letter-spacing: -.01em;
  line-height: 1.1;
  font-size: clamp(30px, 5.2vw, 58px);
  /* the same restrained neon as the titles that survive on the main site */
  text-shadow:
    0 0 5px  rgba(53, 185, 138, .30),
    0 0 16px rgba(53, 185, 138, .20),
    0 0 38px rgba(53, 185, 138, .12);
}
/* T5 — masked heading reveal, identical to site.css. The words start below a
   hard edge and rise into it, with no opacity anywhere: absent, then present.
   Same family as this page's own redaction wipe. */
/* T6 — one mask per WORD, after cielrose.tv. See site.css. */
.rv-w,
.rv-mask {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  /* Padding pulled straight back by an equal negative margin: net zero for
     layout, but it gives the box room the clip can spend. The bottom .12em
     stops a descender being shaved as it lands, and the side .34em keeps the
     T2 neon halo from being sliced off at the mask edge. The BOTTOM edge is
     the one the effect is read from, so it stays tight to the type. */
  padding: 0 .34em .12em;
  margin: 0 -.34em -.12em;
}
.rv-in {
  display: inline-block;
  transform: translateY(110%);
  transition: transform .62s cubic-bezier(.22, 1, .36, 1);
}
.rv-ready .rv-in { transform: none; }
@media (prefers-reduced-motion: reduce) {
  .rv-in { transform: none; transition: none; }
}

/* T6 — rolling labels, identical to site.css. Kept in both sheets rather
   than shared, the same way the cursor is: this page loads privacy.css
   alone, and a hover that behaved differently here would read as a
   different site. */
.roll {
  display: inline-block;
  overflow: hidden;
  vertical-align: bottom;
  position: relative;
}
/* symmetric, not the out-quint used for arrivals — see the note in site.css */
.roll span {
  display: block;
  transition: transform .42s cubic-bezier(.4, 0, .2, 1);
}
.roll .roll-b {
  position: absolute;
  left: 0;
  top: 100%;
  width: 100%;
}
a:hover .roll span,
a:focus-visible .roll span,
button:hover .roll span,
button:focus-visible .roll span { transform: translateY(-100%); }
@media (prefers-reduced-motion: reduce) {
  .roll span { transition: none; }
  a:hover .roll span, a:focus-visible .roll span,
  button:hover .roll span, button:focus-visible .roll span { transform: none; }
}

.updated {
  margin-top: 10px;
  font-family: var(--font-pixel);
  font-size: 10px;
  letter-spacing: .12em;
  text-transform: uppercase;
  color: var(--mist);
}

/* ---- the ledger: the claim as a readout ---- */
.ledger {
  border: 1px solid var(--line-soft);
  background: var(--bg-1);
  padding: 12px 14px;
}
.l-row {
  display: grid;
  /* wide enough for "data sold" on one line — wrapping it made that row
     taller than the other three and threw the whole readout out of step */
  grid-template-columns: 74px 1fr 18px;
  align-items: center;
  gap: 10px;
  white-space: nowrap;
  font-family: var(--font-pixel);
  font-size: 10px;
  letter-spacing: .06em;
  padding: 4px 0;
}
.l-label { color: var(--teal); text-transform: lowercase; }
.l-val { color: var(--emerald); text-align: right; }
.l-bar {
  height: 8px;
  background: var(--bg-0);
  border: 1px solid var(--line-soft);
  overflow: hidden;
}
/* An empty bar is the whole message, so it is drawn as an empty bar. The 2px
   tick at the left keeps the row reading as a live gauge sitting at zero,
   rather than as a component that failed to load. */
.l-bar i {
  display: block;
  height: 100%;
  width: 2px;
  background: var(--emerald-deep);
}

.p-lead {
  max-width: 68ch;
  margin: clamp(18px, 3vh, 30px) 0;
  color: var(--foam);
  font-size: clamp(15px, 1.5vw, 18px);
}

/* ---- the three clauses, side by side ---- */
.clauses {
  list-style: none;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: clamp(12px, 1.6vw, 20px);
}
.clause {
  position: relative;
  padding: 18px 20px 20px;
  background: linear-gradient(158deg, rgba(10, 38, 32, .78), rgba(5, 21, 17, .78));
  border: 1px solid var(--line-soft);
  overflow: hidden;
  /* the cards arrive first, so the bars have something to be sitting on */
  opacity: 0;
  transform: translateY(14px);
  animation: clause-in .5s cubic-bezier(.22, 1, .36, 1) forwards;
  transition: border-color .45s cubic-bezier(.22, 1, .36, 1);
}
@keyframes clause-in { to { opacity: 1; transform: none; } }
.clause:nth-child(1) { animation-delay: .18s; }
.clause:nth-child(2) { animation-delay: .26s; }
.clause:nth-child(3) { animation-delay: .34s; }
/* the signature emerald rule, same as the project and skill cards */
.clause::before {
  content: "";
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  height: 2px;
  background: linear-gradient(90deg, var(--emerald), var(--teal) 70%, transparent);
  transform: scaleX(0);
  transform-origin: left;
  transition: transform .55s cubic-bezier(.22, 1, .36, 1);
}
.clause:hover { border-color: var(--emerald-deep); }
.clause:hover::before { transform: scaleX(1); }
.c-idx {
  display: block;
  font-family: var(--font-pixel);
  font-size: 11px;
  letter-spacing: .16em;
  color: var(--emerald);
  opacity: .55;
  margin-bottom: 12px;
}
.clause h2 {
  font-family: var(--font-pixel);
  font-size: 12.5px;
  line-height: 1.5;
  letter-spacing: .04em;
  text-transform: uppercase;
  font-weight: 400;
  color: var(--foam);
  margin-bottom: 10px;
}
.clause p {
  color: var(--mist);
  font-size: 13.5px;
  line-height: 1.55;
  margin-bottom: 9px;
}
.clause p:last-child { margin-bottom: 0; }
.clause b { color: var(--foam); font-weight: 600; }

a { color: var(--emerald); text-decoration-color: var(--emerald-deep); text-underline-offset: 3px; }
a:hover { text-decoration-color: var(--emerald); }

/* ==========================================================================
   THE DECLASSIFY WIPE
   Each redacted block carries an opaque bar that covers it and then retracts
   to the right, uncovering the type. Not a fade: a hard edge travelling
   across the words, which is what pulling a redaction bar off actually looks
   like, and what keeps this in the site's language rather than being a
   generic reveal.

   `forwards` is load-bearing — it leaves every bar at scaleX(0), so if the
   animation never runs the page is simply readable.
   ========================================================================== */
.redact { position: relative; }
.redact::after {
  content: "";
  position: absolute;
  inset: -2px -6px;
  background: linear-gradient(90deg, #0d3b2e, var(--emerald-deep));
  box-shadow: 0 0 18px rgba(53, 185, 138, .18);
  transform-origin: right;
  animation: declassify .5s cubic-bezier(.72, 0, .18, 1) forwards;
  pointer-events: none;
}
@keyframes declassify {
  from { transform: scaleX(1); }
  to   { transform: scaleX(0); }
}
/* Sequenced by position rather than by an inline custom property: a
   style="--d:…" attribute in the markup is exactly what the production CSP's
   `style-src 'self'` refuses, and this must not depend on one. */
.p-lead.redact::after { animation-delay: .18s; }
.clause:nth-child(1) .redact:nth-of-type(1)::after { animation-delay: .44s; }
.clause:nth-child(1) .redact:nth-of-type(2)::after { animation-delay: .54s; }
.clause:nth-child(2) .redact:nth-of-type(1)::after { animation-delay: .64s; }
.clause:nth-child(2) .redact:nth-of-type(2)::after { animation-delay: .74s; }
.clause:nth-child(3) .redact:nth-of-type(1)::after { animation-delay: .84s; }
.clause:nth-child(3) .redact:nth-of-type(2)::after { animation-delay: .94s; }

/* ---- custom cursor, identical to site.css ---- */
.cursor-dot, .cursor-ring {
  position: fixed;
  top: 0;
  left: 0;
  z-index: 100;
  pointer-events: none;
}
.cursor-dot { translate: -50% -50%; }
.cursor-dot, .cursor-ring { will-change: transform; }
.cursor-dot, .cursor-ring {
  --ink: var(--emerald);
  --glow: rgba(53, 185, 138, .7);
}
.cursor-dot {
  width: 7px;
  height: 7px;
  background: var(--ink);
  box-shadow: 0 0 7px var(--glow);
  /* 2026-08-03: no transition on the lock-on — the block hollows in the
     same frame as the hover. Kept identical across all three cursor copies. */
}
.cursor-dot.hot {
  width: 11px;
  height: 11px;
  background: transparent;
  box-shadow: inset 0 0 0 2px var(--ink), 0 0 10px var(--glow);
}
.cursor-dot.down { --glow: rgba(53, 185, 138, 1); }
.cursor-dot.hot.down { box-shadow: inset 0 0 0 3px var(--ink), 0 0 15px var(--glow); }
.cursor-ring {
  width: 30px;
  height: 30px;
  --arm: 8px;
  --w: 2px;
  --c: rgba(53, 185, 138, .85);
  background:
    linear-gradient(var(--c), var(--c)) 0 0        / var(--arm) var(--w) no-repeat,
    linear-gradient(var(--c), var(--c)) 0 0        / var(--w) var(--arm) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 0     / var(--arm) var(--w) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 0     / var(--w) var(--arm) no-repeat,
    linear-gradient(var(--c), var(--c)) 0 100%     / var(--arm) var(--w) no-repeat,
    linear-gradient(var(--c), var(--c)) 0 100%     / var(--w) var(--arm) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 100%  / var(--arm) var(--w) no-repeat,
    linear-gradient(var(--c), var(--c)) 100% 100%  / var(--w) var(--arm) no-repeat;
  transform-origin: 0 0;
  /* opacity still eases (cursor appearing/leaving); filter does not, so the
     bracket glow lights on the hover frame. */
  transition: opacity .2s ease;
}
.cursor-ring.hot {
  --c: var(--emerald);
  --arm: 9px;
  filter: drop-shadow(0 0 5px rgba(53, 185, 138, .8));
}
.cursor-dot.invert, .cursor-ring.invert { mix-blend-mode: difference; }
.cursor-dot.invert { --glow: transparent; }
.cursor-ring.invert { --c: var(--emerald); --w: 3px; --arm: 10px; filter: none; }
@media (hover: none), (pointer: coarse) {
  .cursor-dot, .cursor-ring { display: none; }
}
.has-cursor, .has-cursor * { cursor: none !important; }

/* ---- click feedback, identical to site.css ---- */
.click-layer { position: fixed; inset: 0; z-index: 99; pointer-events: none; overflow: hidden; }
.click-burst {
  position: absolute;
  width: 14px;
  height: 14px;
  margin: -7px 0 0 -7px;
  border: 2px solid var(--emerald);
  animation: click-burst .42s cubic-bezier(.16, 1, .3, 1) forwards;
}
@keyframes click-burst {
  from { transform: rotate(0deg)  scale(.5);  opacity: .9; }
  to   { transform: rotate(45deg) scale(3.6); opacity: 0;  }
}
.click-burst.invert { mix-blend-mode: difference; }

:focus-visible { outline: 2px solid var(--emerald); outline-offset: 3px; }

/* ---- narrower screens: the clauses stack and the page scrolls ---- */
@media (max-width: 900px) {
  .p-head { grid-template-columns: 1fr; align-items: start; }
  .clauses { grid-template-columns: 1fr; }
  .privacy { align-items: flex-start; }
}
@media (max-width: 560px) {
  .privacy { padding-top: 88px; }
  .clause { padding: 14px 16px 16px; }
}

/* ---- reduced motion: nothing moves, and nothing is ever covered ---- */
@media (prefers-reduced-motion: reduce) {
  .gem { animation: none; transform: rotate(45deg); }
  .redact::after { animation: none; transform: scaleX(0); }
  .clause { animation: none; opacity: 1; transform: none; }
  .cursor-dot, .cursor-ring { transition: none; }
  .click-layer { display: none; }
}

/* ---- print: a plain document ---- */
@media print {
  @page { margin: 16mm 14mm; }
  html, body { background: #fff; color: #111; font-size: 11pt; }
  .bg-bleed, .bg-scan, .cursor-dot, .cursor-ring, .click-layer, .nav, .ledger { display: none !important; }
  .privacy { display: block; min-height: 0; padding: 0; }
  .privacy-inner { width: 100%; }
  .p-head { display: block; border-bottom: 1px solid #ddd; }
  h1 { font-size: 22pt; text-shadow: none; }
  .kicker, .updated { color: #0a6b4c; }
  .clauses { display: block; }
  .clause {
    background: none;
    border: 0;
    border-top: 1px solid #ddd;
    padding: 8pt 0;
    opacity: 1 !important;
    transform: none !important;
    animation: none !important;
    page-break-inside: avoid;
  }
  .clause h2 { color: #111; font-size: 10pt; }
  .clause p, .p-lead { color: #333; }
  .c-idx { color: #0a6b4c; }
  a { color: #0a6b4c; }
  /* a redaction bar must never print over the policy */
  .redact::after { display: none !important; }
}
