/* ==========================================================
   REDAKT
   Palette: near-black ground, one accent per tool.
   The recurring motif is the redaction bar itself - a solid
   block that covers something. It is used for the logo mark,
   the hero, selected rows and the primary button, so the
   interface is built out of the thing the product does.
   ========================================================== */

@import url('https://fonts.googleapis.com/css2?family=Space+Grotesk:wght@400;500;700&family=Inter:wght@400;500;600&family=JetBrains+Mono:wght@400;500&display=swap');

:root {
  color-scheme: dark;
  --ground:      #08080B;
  --surface:     #111116;
  --surface-2:   #17171E;
  --line:        #24242E;
  --text:        #ECECF1;
  --text-dim:    #8B8B99;
  /* Lifted from #5A5A68, which measured 2.78:1 against the surface -
     below the 4.5 needed for small text, and this variable is used for
     exactly that: the limits line, occurrence counts, secondary hints. */
  --text-faint:  #8A8A98;

  --file:   #F5A524;
  --image:  #22D3EE;
  --video:  #C084FC;
  --danger: #F87171;
  --ok:     #4ADE80;

  --accent: var(--file);

  --display: 'Space Grotesk', system-ui, sans-serif;
  --body:    'Inter', system-ui, sans-serif;
  --mono:    'JetBrains Mono', ui-monospace, monospace;

  /* No shadows in dark mode: borders and surface steps already do the
     work, and a dark shadow on a dark background is invisible anyway. */
  --shadow-sm: none;
  --shadow-md: none;

  --r-sm: 8px;
  --r:    14px;
  --r-lg: 22px;
}

/* ---------- light theme ----------
   Only the variables change; every rule below reads them, so nothing
   else needs a light-mode branch.

   The accents are DARKER than their dark-theme counterparts rather than
   the same hue. #F5A524 on white is roughly 2:1 contrast, which is
   unreadable as text; the amber-700 below clears 4.5:1. A palette that
   just swaps background for foreground and keeps the accents produces
   the washed-out look most hand-rolled light modes have. */
:root[data-theme="light"] {
  color-scheme: light;

  /* Neutrals carry a slight cool cast rather than being pure grey. Flat
     greys on a white page read as unfinished; a few points of blue in the
     mix is what makes a light interface look deliberate. */
  --ground:      #FAFAFC;
  --surface:     #FFFFFF;
  --surface-2:   #F4F5F8;
  --line:        #E4E6EC;
  --text:        #14141A;
  --text-dim:    #52525E;
  --text-faint:  #6B6B75;

  /* Cleaner accent hues. #B45309 is a muddy brown-orange on white; these
     are more saturated at the same contrast, so they read as colour
     rather than as dirt. All still clear 4.5:1 against the surface. */
  --file:   #C2410C;
  --image:  #0369A1;
  --video:  #6D28D9;
  --danger: #BE123C;
  --ok:     #15803D;

  --accent: var(--file);

  /* THE THING LIGHT MODE ACTUALLY NEEDED.
     In dark mode a 1px border plus a lighter surface reads as depth,
     because the eye expects light to come from above onto a dark scene.
     On a white page that same treatment is invisible and everything
     collapses into one flat sheet - which is what "the colour feels off"
     usually turns out to be. Elevation has to come from shadow instead. */
  --shadow-sm: 0 1px 2px rgba(16, 18, 32, .04),
               0 1px 3px rgba(16, 18, 32, .06);
  --shadow-md: 0 2px 4px rgba(16, 18, 32, .04),
               0 8px 20px rgba(16, 18, 32, .07);
}

:root[data-theme="light"] .wash {
  /* 4% not 7%, and stopped higher up. An accent wash that is readable on
     a light background stops looking like light and starts looking like a
     stain across the top of the page. */
  background:
    radial-gradient(52rem 30rem at 50% -12%,
      color-mix(in srgb, var(--accent) 4%, transparent), transparent 65%);
}

/* The mode swatches and the emoji preview sit on white in light mode, so
   they need their own border to read as controls rather than as marks on
   the page. */
:root[data-theme="light"] .mode-swatch.blur {
  background: linear-gradient(90deg, #C9CBD4, #E6E8EF, #C9CBD4);
}
:root[data-theme="light"] .preview { background: var(--surface-2); }
:root[data-theme="light"] dialog { box-shadow: var(--shadow-md); }
:root[data-theme="light"] dialog::backdrop { background: rgba(20, 20, 26, .42); }

/* A redaction bar is drawn with var(--text), which is near-black in both
   themes - so the motif survives the theme switch without a special
   case, which is why it was built from a text colour rather than a
   hard-coded black. */

* { box-sizing: border-box; }

html, body {
  margin: 0;
  padding: 0;
  background: var(--ground);
  color: var(--text);
  font-family: var(--body);
  -webkit-font-smoothing: antialiased;
}

body {
  min-height: 100vh;
  display: flex;
  flex-direction: column;
}

a { color: inherit; }

/* ---------- ambient wash ---------- */
.wash {
  position: fixed;
  inset: 0;
  pointer-events: none;
  z-index: 0;
  background:
    radial-gradient(60rem 40rem at 50% -10%, color-mix(in srgb, var(--accent) 13%, transparent), transparent 70%),
    radial-gradient(40rem 30rem at 90% 10%, rgba(192,132,252,.07), transparent 70%);
}

/* ---------- shell ---------- */
.topbar {
  position: relative;
  z-index: 2;
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 1.1rem 1.6rem;
  border-bottom: 1px solid var(--line);
}

.brand {
  display: inline-flex;
  align-items: center;
  gap: .55rem;
  font-family: var(--display);
  font-weight: 700;
  font-size: 1.02rem;
  letter-spacing: -.02em;
  text-decoration: none;
}

/* the logo mark IS a redaction bar */
.brand-mark {
  width: 30px;
  height: 13px;
  border-radius: 3px;
  background: var(--text);
  display: inline-block;
}

.topbar-right {
  display: flex;
  align-items: center;
  gap: .7rem;
  font-size: .85rem;
}

.chip {
  font-family: var(--mono);
  font-size: .72rem;
  color: var(--text-dim);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .3rem .7rem;
  background: var(--surface);
}

main {
  position: relative;
  z-index: 1;
  flex: 1;
  width: 100%;
  max-width: 1080px;
  margin: 0 auto;
  padding: 2.4rem 1.4rem 4rem;
}

footer {
  position: relative;
  z-index: 1;
  text-align: center;
  padding: 1.6rem;
  color: var(--text-faint);
  font-size: .78rem;
  border-top: 1px solid var(--line);
}

/* The one line on the page that is a promise rather than a label, and it
   was set in the faintest colour available. Now it reads as a statement:
   full text colour, a little larger, on a tinted plate so the eye stops
   at it instead of sliding past. */
footer {
  color: var(--text);
  font-size: .88rem;
  font-weight: 500;
  padding: 1.5rem 1.2rem 2rem;
}
footer::before {
  content: "";
  display: inline-block;
  width: 26px;
  height: 11px;
  border-radius: 2px;
  background: var(--text);
  vertical-align: -1px;
  margin-right: .6rem;
}

/* ---------- hero ---------- */
.hero { text-align: center; padding: 3.5rem 0 2.8rem; }

.hero h1 {
  font-family: var(--display);
  font-weight: 700;
  font-size: clamp(2rem, 5.5vw, 3.3rem);
  line-height: 1.08;
  letter-spacing: -.035em;
  margin: 0 0 1rem;
}

/* signature element: a real redaction bar over live text.
   Hover or focus lifts it - the product demonstrated in one gesture. */
.redacted {
  position: relative;
  display: inline-block;
  cursor: default;
}
.redacted > span { opacity: 0; transition: opacity .28s ease; }
.redacted::after {
  content: "";
  position: absolute;
  /* Left edge flush with the word, not pushed out into the space before
     it. With a negative left inset the bar began between "be" and
     "there" and grew from there, so it read as starting after the
     previous word rather than over this one. */
  inset: .07em -.22em .1em 0;
  background: var(--text);
  border-radius: 3px;
  transform-origin: left center;
  transition: transform .34s cubic-bezier(.2,.8,.2,1);
}
.redacted:hover > span,
.redacted:focus-visible > span { opacity: 1; }
.redacted:hover::after,
.redacted:focus-visible::after { transform: scaleX(0); }

.hero p {
  max-width: 34rem;
  margin: 0 auto;
  color: var(--text-dim);
  font-size: 1.02rem;
  line-height: 1.6;
}

/* ---------- tool cards ---------- */
.tools {
  display: grid;
  grid-template-columns: repeat(auto-fit, minmax(240px, 1fr));
  /* Wider than it needs to be for layout. At 1.1rem the blooms of
     adjacent cards ran into each other and read as one smear rather than
     three lit objects. */
  gap: 2rem;
  margin-top: 3rem;
}

.tool-card {
  --c: var(--file);
  position: relative;
  display: block;
  padding: 1.5rem 1.4rem 1.6rem;
  border-radius: var(--r-lg);
  background: var(--surface);
  border: 1px solid var(--line);
  text-decoration: none;
  overflow: hidden;
  transition: transform .22s ease, border-color .22s ease;
}
.tool-card { box-shadow: var(--shadow-sm); }
.tool-card:hover {
  transform: translateY(-3px);
  border-color: color-mix(in srgb, var(--c) 45%, var(--line));
  box-shadow: var(--shadow-md);
}
.tool-card:focus-visible { outline: 2px solid var(--c); outline-offset: 3px; }

.tool-card::before {
  content: "";
  position: absolute;
  inset: -40% -20% auto -20%;
  height: 70%;
  background: radial-gradient(closest-side, color-mix(in srgb, var(--c) 35%, transparent), transparent);
  /* Dimmed. A bright rim only reads as light if the surface it is
     wrapping stays dark - lifting the inside too washes the contrast out
     and the whole thing goes flat. */
  opacity: .22;
  transition: opacity .22s ease;
}
.tool-card:hover::before { opacity: .9; }
/* The card glow is a dark-mode device - a coloured haze bleeding out of
   the top of a white card looks like a printing error. Light mode gets a
   crisp tinted top edge instead, which carries the same per-tool colour
   coding without the mess. */
:root[data-theme="light"] .tool-card::before {
  inset: 0 0 auto 0;
  height: 3px;
  border-radius: var(--r-lg) var(--r-lg) 0 0;
  background: var(--c);
  opacity: .85;
}
:root[data-theme="light"] .tool-card:hover::before { opacity: 1; }

.tool-card[data-tool="image"] { --c: var(--image); }
.tool-card[data-tool="video"] { --c: var(--video); }

.tool-icon {
  position: relative;
  width: 40px; height: 40px;
  border-radius: 11px;
  display: grid; place-items: center;
  background: color-mix(in srgb, var(--c) 16%, var(--surface-2));
  border: 1px solid color-mix(in srgb, var(--c) 34%, transparent);
  color: var(--c);
  margin-bottom: 1rem;
}

.tool-card h2 {
  position: relative;
  font-family: var(--display);
  font-size: 1.12rem;
  letter-spacing: -.02em;
  margin: 0 0 .4rem;
}
.tool-card p {
  position: relative;
  margin: 0;
  color: var(--text-dim);
  font-size: .87rem;
  line-height: 1.55;
}

/* ---------- tool page ---------- */
.tool-head {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 1rem;
  margin-bottom: 1.6rem;
}
.tool-head h1 {
  font-family: var(--display);
  font-size: 1.7rem;
  letter-spacing: -.03em;
  margin: 0 0 .35rem;
}
.tool-head p { margin: 0; color: var(--text-dim); font-size: .9rem; }

.help-btn {
  flex: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text-dim);
  font-family: var(--display);
  font-weight: 700;
  cursor: pointer;
  transition: border-color .2s, color .2s;
}
.help-btn:hover { border-color: var(--accent); color: var(--accent); }

.panel {
  background: var(--surface);
  border: 1px solid var(--line);
  border-radius: var(--r-lg);
  padding: 1.5rem;
  box-shadow: var(--shadow-md);
}

/* ---------- dropzone ---------- */
.drop {
  border: 1.5px dashed var(--line);
  border-radius: var(--r);
  padding: 2.4rem 1.4rem;
  text-align: center;
  cursor: pointer;
  background: var(--surface-2);
  transition: border-color .2s, background .2s;
}
.drop:hover, .drop.over { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 7%, var(--surface-2)); }
.drop strong { display: block; font-size: .98rem; margin-bottom: .3rem; }
.drop span { color: var(--text-dim); font-size: .85rem; }

.limits {
  margin-top: .7rem;
  font-family: var(--mono);
  font-size: .73rem;
  color: var(--text-faint);
  text-align: center;
  line-height: 1.6;
}

.filelist { margin-top: 1rem; display: grid; gap: .5rem; }
.filerow {
  display: flex; align-items: center; justify-content: space-between;
  gap: .8rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: .6rem .8rem;
  font-size: .85rem;
}
.filerow .sz { font-family: var(--mono); font-size: .72rem; color: var(--text-faint); }

/* ---------- form controls ---------- */
label.field { display: block; margin-top: 1.3rem; }
label.field > span {
  display: block;
  font-size: .82rem;
  color: var(--text-dim);
  margin-bottom: .45rem;
}

textarea, input[type=text], input[type=email], input[type=password] {
  width: 100%;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  color: var(--text);
  font-family: var(--body);
  font-size: .92rem;
  padding: .75rem .85rem;
  resize: vertical;
}
textarea:focus, input:focus { outline: none; border-color: var(--accent); }
textarea::placeholder, input::placeholder { color: var(--text-faint); }

.btn {
  box-shadow: var(--shadow-sm);
  display: inline-flex;
  align-items: center;
  justify-content: center;
  gap: .5rem;
  border: none;
  border-radius: var(--r-sm);
  padding: .8rem 1.3rem;
  font-family: var(--body);
  font-weight: 600;
  font-size: .9rem;
  cursor: pointer;
  background: var(--text);
  color: var(--ground);
  transition: opacity .2s, transform .12s;
}
.btn:hover { opacity: .88; }
.btn:active { transform: translateY(1px); }
.btn:disabled { opacity: .38; cursor: not-allowed; }
.btn.ghost { background: transparent; color: var(--text); border: 1px solid var(--line); box-shadow: none; }
.btn.ghost:hover { border-color: var(--accent); }
.btn-row { display: flex; gap: .7rem; flex-wrap: wrap; margin-top: 1.4rem; }

/* ---------- mode picker ---------- */
.modes { display: grid; grid-template-columns: repeat(3, 1fr); gap: .7rem; margin-top: .5rem; }
.mode {
  position: relative;
  border: 1px solid var(--line);
  background: var(--surface-2);
  border-radius: var(--r);
  padding: 1rem .8rem;
  cursor: pointer;
  text-align: center;
  transition: border-color .2s, background .2s;
}
.mode:hover { border-color: color-mix(in srgb, var(--accent) 50%, var(--line)); }
.mode input { position: absolute; opacity: 0; pointer-events: none; }
.mode-swatch {
  width: 46px; height: 30px;
  margin: 0 auto .55rem;
  border-radius: 6px;
  /* Was #2A2A34 - a dark square, which read as a filled block rather
     than an empty swatch once the page went light. */
  background: var(--surface-2);
  border: 1px solid var(--line);
  display: grid; place-items: center;
  font-size: 1.15rem;
  overflow: hidden;
}
.mode-swatch.box { background: #000; }
.mode-swatch.blur { background: linear-gradient(90deg,#4a4a58,#7b7b8d,#4a4a58); filter: blur(2.5px); }
.mode strong { display: block; font-size: .86rem; font-weight: 600; }
.mode small { color: var(--text-faint); font-size: .72rem; }
.mode input:checked ~ .mode-body strong { color: var(--accent); }
.mode:has(input:checked) { border-color: var(--accent); background: color-mix(in srgb, var(--accent) 9%, var(--surface-2)); }

/* ---------- review list ---------- */
.tier { margin-top: 1.5rem; }
.tier-head {
  display: flex; align-items: baseline; justify-content: space-between;
  gap: .8rem; margin-bottom: .55rem;
}
.tier-head h3 {
  font-family: var(--display);
  font-size: .82rem;
  text-transform: uppercase;
  letter-spacing: .09em;
  color: var(--text-dim);
  margin: 0;
}
.tier-head span { font-family: var(--mono); font-size: .72rem; color: var(--text-faint); }

.rows { display: grid; gap: .4rem; }
.row {
  display: flex; align-items: center; gap: .8rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: .6rem .8rem;
  cursor: pointer;
  transition: border-color .18s, background .18s;
}
.row:hover { border-color: color-mix(in srgb, var(--accent) 40%, var(--line)); }
.row input { accent-color: var(--accent); width: 16px; height: 16px; flex: none; cursor: pointer; }

/* selected rows wear the redaction bar */
.row-text {
  flex: 1;
  font-family: var(--mono);
  font-size: .82rem;
  min-width: 0;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
  position: relative;
}
.row:has(input:checked) .row-text { color: var(--text-faint); }
.row:has(input:checked) .row-text::after {
  content: "";
  position: absolute;
  inset: .12em -.15em .15em -.15em;
  background: var(--text);
  border-radius: 2px;
  opacity: .92;
}

.row-tag {
  font-size: .7rem;
  font-family: var(--mono);
  color: var(--text-dim);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .16rem .55rem;
  flex: none;
}
.row-count { font-family: var(--mono); font-size: .72rem; color: var(--text-faint); flex: none; }

/* ---------- notes / alerts ---------- */
.note {
  border-radius: var(--r-sm);
  padding: .7rem .9rem;
  font-size: .84rem;
  line-height: 1.5;
  margin-bottom: .5rem;
  border: 1px solid var(--line);
  background: var(--surface-2);
}
.note.warn { border-color: color-mix(in srgb, var(--danger) 45%, var(--line)); }
.note.good { border-color: color-mix(in srgb, var(--ok) 40%, var(--line)); }
.note code { font-family: var(--mono); font-size: .8rem; color: var(--accent); }

.status { margin-top: 1rem; font-size: .86rem; color: var(--text-dim); min-height: 1.3em; }

/* ---------- modal ---------- */
dialog {
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text);
  border-radius: var(--r-lg);
  padding: 0;
  max-width: 520px;
  width: calc(100% - 2rem);
}
dialog::backdrop { background: rgba(0,0,0,.72); backdrop-filter: blur(3px); }
.modal-in { padding: 1.6rem; }
.modal-in h2 { font-family: var(--display); font-size: 1.15rem; margin: 0 0 1rem; letter-spacing: -.02em; }
.steps { margin: 0; padding: 0; list-style: none; counter-reset: s; display: grid; gap: .9rem; }
.steps li { display: flex; gap: .8rem; font-size: .88rem; line-height: 1.55; color: var(--text-dim); }
.steps li::before {
  counter-increment: s;
  content: counter(s);
  flex: none;
  width: 22px; height: 22px;
  border-radius: 6px;
  background: var(--surface-2);
  border: 1px solid var(--line);
  color: var(--accent);
  font-family: var(--mono);
  font-size: .72rem;
  display: grid; place-items: center;
}
.steps strong { color: var(--text); font-weight: 600; }

/* ---------- auth ---------- */
.auth-wrap { max-width: 380px; margin: 3.5rem auto; }
.auth-wrap h1 { font-family: var(--display); font-size: 1.6rem; letter-spacing: -.03em; margin: 0 0 .4rem; }
.auth-wrap > p { color: var(--text-dim); font-size: .88rem; margin: 0 0 1.5rem; }
.auth-alt { margin-top: 1.2rem; font-size: .84rem; color: var(--text-dim); text-align: center; }
.auth-alt a { color: var(--accent); }

.privacy-note {
  margin-top: 1.4rem;
  padding: .85rem .95rem;
  border-radius: var(--r-sm);
  background: var(--surface-2);
  border: 1px solid var(--line);
  font-size: .8rem;
  line-height: 1.55;
  color: var(--text-dim);
}

/* ---------- small screens ---------- */
@media (max-width: 640px) {
  main { padding: 1.4rem 1rem 3rem; }

  .modes { grid-template-columns: 1fr; }
  .tool-head { flex-direction: row; align-items: flex-start; }
  .tool-head h1 { font-size: 1.4rem; }

  /* The topbar carries brand + email + processed count + sign out, which
     runs off the side of a phone. The chips wrap under the brand and the
     email truncates rather than pushing the button off-screen. */
  .topbar { flex-wrap: wrap; gap: .6rem; padding: .9rem 1rem; }
  .topbar-right { width: 100%; justify-content: flex-start; flex-wrap: wrap; }
  .chip { max-width: 58vw; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  .hero { padding: 2rem 0 1.8rem; }
  .hero p { font-size: .95rem; }

  .panel { padding: 1.1rem; border-radius: var(--r); }
  .drop { padding: 1.8rem 1rem; }
  .limits { font-size: .68rem; }

  /* Buttons go full width and stack: side-by-side on a 360px screen puts
     "Download" under a thumb reaching for "Apply changes". */
  .btn-row { flex-direction: column; }
  .btn-row .btn { width: 100%; }

  /* A rendered page at phone width is unreadable, so the preview gets
     more height and horizontal scroll rather than shrinking further. */
  .preview { max-height: 60vh; padding: .8rem; font-size: .8rem; }
  .preview .page-img { border-radius: 4px; }

  .chip-item { font-size: .72rem; max-width: 100%; }
  .chip-item span:first-child { overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }

  .filerow { flex-wrap: wrap; gap: .3rem; }
  .filerow span:first-child { max-width: 100%; word-break: break-all; }

  dialog { width: calc(100% - 1.2rem); }
  .modal-in { padding: 1.2rem; }
  .steps li { font-size: .84rem; }

  .auth-wrap { margin: 1.6rem auto; }
}

/* Very narrow phones */
@media (max-width: 380px) {
  .hero h1 { font-size: 1.7rem; }
  .tools { gap: .8rem; }
  .tool-card { padding: 1.2rem 1.1rem; }
}

/* Landscape phone / small tablet: keep the tool cards from becoming one
   very long column. */
@media (min-width: 641px) and (max-width: 900px) {
  .tools { grid-template-columns: repeat(2, 1fr); }
}

@media (prefers-reduced-motion: reduce) {
  * { animation: none !important; transition: none !important; }
  .redacted > span { opacity: 1; }
  .redacted::after { display: none; }
}


/* ---------- removed-item chips ---------- */
.chips { display: flex; flex-wrap: wrap; gap: .45rem; }

@media (max-width: 640px) {
  /* The add-term input and its button sit side by side; on a phone the
     input collapses to nothing, so they stack. */
  label.field > div[style*="flex"] { flex-direction: column !important; }
  label.field > div[style*="flex"] .btn { width: 100%; }
}
.chip-item {
  display: inline-flex; align-items: center; gap: .45rem;
  font-family: var(--mono); font-size: .78rem;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: 999px;
  padding: .35rem .7rem .35rem .8rem;
  cursor: pointer;
  transition: border-color .18s, opacity .18s;
}
.chip-item:hover { border-color: var(--danger); }
.chip-item .x { color: var(--text-faint); font-size: .9rem; line-height: 1; }
.chip-item:hover .x { color: var(--danger); }
.chip-item.dropped { opacity: .4; text-decoration: line-through; border-style: dashed; }

/* ---------- redacted preview ---------- */
.preview {
  max-height: 26rem;
  overflow-y: auto;
  background: var(--surface-2);
  border: 1px solid var(--line);
  border-radius: var(--r-sm);
  padding: 1rem 1.1rem;
  font-size: .84rem;
  line-height: 1.65;
  white-space: pre-wrap;
  word-break: break-word;
  color: var(--text-dim);
}
.preview .blk { padding: .5rem 0; border-bottom: 1px solid var(--line); }
.preview .blk:last-child { border-bottom: none; }
/* The redaction character rendered as an actual bar, so a page of them
   reads as redaction rather than as a wall of unknown glyphs. */
.preview mark {
  background: var(--text);
  color: var(--text);
  border-radius: 2px;
  padding: 0 .1em;
}

.preview .page-img {
  display: block;
  width: 100%;
  height: auto;
  border-radius: var(--r-sm);
  border: 1px solid var(--line);
  margin-bottom: .8rem;
  background: #fff;
}


/* ---------- tool switcher in the header ----------
   Added so someone can move between Documents, Photos and Video without
   going back to the landing page first. */
/* ---------- theme toggle ---------- */
.theme-btn {
  flex: none;
  width: 34px; height: 34px;
  border-radius: 50%;
  border: 1px solid var(--line);
  background: var(--surface);
  color: var(--text-dim);
  cursor: pointer;
  display: grid; place-items: center;
  transition: border-color .2s, color .2s;
}
.theme-btn:hover { border-color: var(--accent); color: var(--accent); }
.theme-btn svg { width: 15px; height: 15px; }
/* Sun shown in dark mode (click for light), moon in light mode. */
.theme-btn .sun { display: block; }
.theme-btn .moon { display: none; }
:root[data-theme="light"] .theme-btn .sun { display: none; }
:root[data-theme="light"] .theme-btn .moon { display: block; }

.toolnav { display: flex; gap: .2rem; }
.toolnav a {
  text-decoration: none;
  font-size: .84rem;
  color: var(--text-dim);
  padding: .42rem .8rem;
  border-radius: 999px;
  border: 1px solid transparent;
  transition: color .18s, border-color .18s, background .18s;
}
.toolnav a:hover { color: var(--text); }
.toolnav a.on {
  color: var(--text);
  background: var(--surface);
  border-color: var(--line);
}

@media (max-width: 640px) {
  .toolnav { width: 100%; justify-content: space-between; }
  .toolnav a { flex: 1; text-align: center; padding: .5rem .3rem; font-size: .8rem; }
}


/* ---------- face picker ---------- */
.face-stage {
  position: relative;
  display: inline-block;
  max-width: 100%;
  line-height: 0;
  border-radius: var(--r-sm);
  overflow: hidden;
  border: 1px solid var(--line);
  /* No touch-action override. It used to be `none`, so that a drag drew
     a box - which also meant a swipe over the photo could not scroll the
     page on a phone. Selection is by tapping a box now, so the browser
     keeps its own gestures. */
}
.face-stage img { display: block; max-width: 100%; height: auto; }

.face-box {
  position: absolute;
  border: 2px solid var(--accent);
  background: color-mix(in srgb, var(--accent) 18%, transparent);
  cursor: pointer;
  box-sizing: border-box;
}
/* A face that WILL be covered is shown solid, matching the result. Off
   is an outline only - so the picture reads the way the output will. */
.face-box.on { background: rgba(0,0,0,.82); border-color: rgba(0,0,0,.9); }
.face-box.off { background: transparent; border-style: dashed; opacity: .8; }
.face-box .tag {
  position: absolute;
  top: -1px; left: -1px;
  font-family: var(--mono);
  font-size: .62rem;
  padding: 0 .25rem;
  background: var(--accent);
  color: #000;
  border-radius: 2px 0 2px 0;
}
.face-box.on .tag { opacity: .55; }


/* Per-photo tabs in the face picker, shown only for a batch. */
.facetabs { display: flex; flex-wrap: wrap; gap: .4rem; margin-bottom: .8rem; }
.facetab {
  font-family: var(--mono);
  font-size: .72rem;
  padding: .35rem .7rem;
  border-radius: 999px;
  border: 1px solid var(--line);
  background: var(--surface-2);
  color: var(--text-dim);
  cursor: pointer;
}
.facetab:hover { border-color: var(--accent); color: var(--text); }
.facetab.on {
  background: var(--accent);
  border-color: var(--accent);
  color: #000;
}


/* ---------- face thumbnails with tickboxes ----------
   Picking from a row of faces is far easier than hunting small boxes on a
   19-person photo, especially on a phone. The photo below stays, for
   context next to the thumbnails. */
.facegrid {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(84px, 1fr));
  gap: .6rem;
  margin-bottom: 1.1rem;
}
.facecard {
  position: relative;
  border: 2px solid var(--line);
  border-radius: var(--r-sm);
  overflow: hidden;
  cursor: pointer;
  background: var(--surface-2);
  transition: border-color .15s, opacity .15s;
}
.facecard img { display: block; width: 100%; height: auto; }
.facecard.on { border-color: var(--accent); }
/* Unticked faces are dimmed AND greyed, so the state is readable without
   relying on the border colour alone. */
.facecard.off { opacity: .45; }
.facecard.off img { filter: grayscale(1); }
.facecard .mark {
  position: absolute;
  top: 4px; right: 4px;
  width: 20px; height: 20px;
  border-radius: 5px;
  display: grid; place-items: center;
  font-size: .74rem;
  font-weight: 700;
  background: var(--surface);
  border: 1px solid var(--line);
  color: var(--text-faint);
}
.facecard.on .mark { background: var(--accent); border-color: var(--accent); color: #000; }
.facecard .num {
  position: absolute;
  bottom: 0; left: 0;
  font-family: var(--mono);
  font-size: .62rem;
  padding: 0 .3rem;
  background: rgba(0,0,0,.6);
  color: #fff;
  border-radius: 0 4px 0 0;
}


/* ==========================================================
   VIVID ACCENTS
   ==========================================================
   Two accents per tool, deliberately.

   --file / --image / --video are the CONTRAST-SAFE pair, used
   anywhere a colour carries text or an icon glyph. They clear
   4.5:1 against the surface in both themes, which is why the
   light versions are as deep as they are.

   --*-vivid is the DECORATIVE pair, used only for glows, card
   edges, washes and tinted fills - things with no reading to
   do. Freed from the contrast floor they can be as saturated
   as they like, which is what stops a light page looking like
   it has had the colour drained out of it.

   Never put text on a vivid. That is the whole point of the
   split.
   ========================================================== */
:root {
  --file-vivid:  #FFB020;
  --image-vivid: #2DE2F5;
  --video-vivid: #CE9BFF;
  --accent-vivid: var(--file-vivid);

  /* Neutral grey rather than --text, so the bar stays visible over a
     pure-black swatch AND a white one. A redaction bar drawn in the text
     colour disappears against the black-box swatch in light mode. */
  --redact-veil: rgba(206, 208, 220, .55);
}
:root[data-theme="light"] {
  --file-vivid:  #F97316;
  --image-vivid: #0EA5E9;
  --video-vivid: #8B5CF6;
  --accent-vivid: var(--file-vivid);
  --redact-veil: rgba(96, 99, 112, .62);
}
/* Listed twice on purpose. `:root[data-theme="light"]` above scores
   0,2,0 and would otherwise beat a single-attribute tool selector at
   0,1,1, leaving every tool page orange in light mode. */
html[data-tool="image"] { --accent-vivid: var(--image-vivid); }
html[data-tool="video"] { --accent-vivid: var(--video-vivid); }
html[data-theme="light"][data-tool="image"] { --accent-vivid: var(--image-vivid); }
html[data-theme="light"][data-tool="video"] { --accent-vivid: var(--video-vivid); }


/* ==========================================================
   LANDING — the redacted page
   ==========================================================
   The whole viewport is a page being redacted: three columns
   of faint type running edge to edge and past the bottom, with
   bars wiping across them and lifting again. Fixed rather than
   scrolled, so it behaves like a surface the page sits on.
   ========================================================== */

:root {
  --pf-ink: rgba(236, 236, 241, .062);
  --pf-bar: rgba(236, 236, 241, .10);
}
:root[data-theme="light"] {
  /* Higher than the dark values, not lower. Dark ink on a light ground
     reads heavier at equal alpha, but .05 was so faint the field simply
     was not there - the page looked blank rather than quiet. */
  --pf-ink: rgba(28, 30, 48, .075);
  --pf-bar: rgba(28, 30, 48, .11);
}

/* ---------- atmosphere ----------
   Not scoped to the landing page any more. The field and the wash are on
   every page, and because both read --accent-vivid, each tool arrives in
   its own colour without a per-page rule. */
.wash {
  background:
    radial-gradient(52rem 32rem at 50% -14%,
      color-mix(in srgb, var(--accent-vivid) 17%, transparent), transparent 66%),
    radial-gradient(40rem 30rem at 92% 4%, rgba(206, 155, 255, .10), transparent 68%),
    radial-gradient(44rem 30rem at 4% 42%, rgba(45, 226, 245, .07), transparent 70%),
    radial-gradient(56rem 34rem at 50% 108%, rgba(255, 176, 32, .07), transparent 68%);
}

/* Light mode gets MORE colour than dark, not less. A white page with a
   4% tint reads as an unstyled default; the washes below are what give
   it a temperature and a direction of light. */
:root[data-theme="light"] .wash {
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--accent-vivid) 11%, transparent) 0%,
      color-mix(in srgb, var(--accent-vivid) 3%, transparent) 22%,
      transparent 44%),
    radial-gradient(48rem 28rem at 50% -12%,
      color-mix(in srgb, var(--accent-vivid) 15%, transparent), transparent 62%),
    radial-gradient(38rem 26rem at 94% 8%, rgba(139, 92, 246, .13), transparent 66%),
    radial-gradient(42rem 28rem at 2% 46%, rgba(14, 165, 233, .11), transparent 68%),
    radial-gradient(60rem 32rem at 50% 106%, rgba(249, 115, 22, .09), transparent 66%);
}
/* A horizon at the foot of a light page. Without it the sheet runs off
   the bottom of the screen with nothing to stop it. */
html[data-theme="light"] body::after {
  content: "";
  position: fixed;
  inset: auto 0 0 0;
  height: 24vh;
  pointer-events: none;
  z-index: 0;
  background: linear-gradient(180deg, transparent, rgba(28, 30, 48, .045));
}

.hero {
  position: relative;
  z-index: 0;
  padding: 5.2rem 0 3.4rem;
}
.hero-body { position: relative; z-index: 1; }

/* ---------- the page behind everything ---------- */
.pagefield {
  position: fixed;
  inset: 0;
  z-index: 0;
  pointer-events: none;
  overflow: hidden;
  /* Fades only at the outer edge now. The earlier mask pulled in to 62%
     and left the sides and the bottom of the screen empty. */
  -webkit-mask-image: radial-gradient(112% 104% at 50% 40%, #000 46%, transparent 96%);
          mask-image: radial-gradient(112% 104% at 50% 40%, #000 46%, transparent 96%);
}

.pf-cols {
  position: absolute;
  inset: -6vh -2vw -2vh;
  display: grid;
  grid-template-columns: repeat(3, 1fr);
  gap: 3.4rem;
  align-content: start;
}
.pf-col { display: flex; flex-direction: column; gap: 1.5rem; }
.pf-col:nth-child(2) { padding-top: 7rem; }
.pf-col:nth-child(3) { padding-top: 2.5rem; }

/* One paragraph. The repeating gradient draws the lines of type, so a
   block of text costs one element rather than one per line. */
.pf-para {
  display: block;
  height: calc(var(--lines) * 20px);
  background: repeating-linear-gradient(
    to bottom,
    var(--pf-ink) 0 6px,
    transparent 6px 20px
  );
  border-radius: 2px;
}
.pf-para:nth-child(even) { width: 93%; }
.pf-short { width: 58% !important; }

/* A bar wiping across the page. Static state is fully drawn, so when
   animation is switched off the page is simply a redacted one. */
.pf-bar {
  position: absolute;
  left: var(--x);
  top: var(--y);
  width: var(--w);
  height: 13px;
  border-radius: 3px;
  background: var(--pf-bar);
  transform: scaleX(1);
  transform-origin: left center;
  animation: pf-wipe 17s var(--d) cubic-bezier(.3, .8, .2, 1) infinite;
}

@keyframes pf-wipe {
  0%   { transform: scaleX(0); }
  5%   { transform: scaleX(1); }
  44%  { transform: scaleX(1); }
  51%  { transform: scaleX(0); }
  100% { transform: scaleX(0); }
}

/* ---------- arrival ----------
   These animate `translate` and `opacity`, deliberately NOT `transform`:
   the cards use transform for their hover lift, and an animation with a
   fill mode would win the cascade and freeze it. */
@keyframes pf-rise {
  from { opacity: 0; translate: 0 14px; }
  to   { opacity: 1; translate: none; }
}
@keyframes pf-cover {
  from { transform: scaleX(0); }
  to   { transform: scaleX(1); }
}
/* The word under the hero bar is opacity 0 at rest, so without this the
   intro would show an empty gap before the bar arrived. Held visible
   through the delay, then it goes - which is the gesture. */
@keyframes pf-conceal {
  from { opacity: 1; }
  to   { opacity: 0; }
}

.hero h1          { animation: pf-rise .68s cubic-bezier(.2,.8,.2,1) both; }
.hero p           { animation: pf-rise .68s .1s cubic-bezier(.2,.8,.2,1) both; }
/* `backwards`, not `both`: once it has run the base style takes over
   again, so hover-to-reveal still works. */
.redacted::after  { animation: pf-cover   .46s .52s cubic-bezier(.2,.8,.2,1) backwards; }
.redacted > span  { animation: pf-conceal .46s .52s cubic-bezier(.2,.8,.2,1) backwards; }

.tools .tool-card { animation: pf-rise .6s cubic-bezier(.2,.8,.2,1) both; }
.tools .tool-card:nth-child(1) { animation-delay: .20s; }
.tools .tool-card:nth-child(2) { animation-delay: .28s; }
.tools .tool-card:nth-child(3) { animation-delay: .36s; }
.landing-note {
  max-width: 44rem;
  margin: 2.6rem auto 0;
  animation: pf-rise .6s .46s cubic-bezier(.2,.8,.2,1) both;
}

/* ---------- tool cards, brighter ---------- */
.tool-card[data-tool="file"]  { --cv: var(--file-vivid); }
.tool-card[data-tool="image"] { --cv: var(--image-vivid); }
.tool-card[data-tool="video"] { --cv: var(--video-vivid); }
.tool-card::before { background: radial-gradient(closest-side,
  color-mix(in srgb, var(--cv) 42%, transparent), transparent); }
.tool-icon {
  background: color-mix(in srgb, var(--cv) 20%, var(--surface-2));
  border-color: color-mix(in srgb, var(--cv) 42%, transparent);
}
:root[data-theme="light"] .tool-card::before {
  background: linear-gradient(90deg,
    var(--cv), color-mix(in srgb, var(--cv) 55%, transparent));
}
:root[data-theme="light"] .tool-card:hover {
  box-shadow: var(--shadow-md),
              0 14px 34px -18px color-mix(in srgb, var(--cv) 65%, transparent);
}


/* ==========================================================
   MODE PICKER — unchosen options are redacted
   ==========================================================
   Choosing a mode is the product's own gesture: the options
   you did not pick are covered by a bar, and the one you did
   pick is uncovered. The veil is partial, not opaque, so the
   swatches stay legible enough to choose between - a picker
   you cannot read is not a picker.
   ========================================================== */
.mode { overflow: hidden; }
.mode-swatch { position: relative; transition: transform .34s cubic-bezier(.2,.8,.2,1); }

.mode-swatch::after {
  content: "";
  position: absolute;
  inset: 0;
  background: var(--redact-veil);
  transform: scaleX(1);
  transform-origin: left center;
  transition: transform .38s cubic-bezier(.2,.8,.2,1), opacity .26s ease;
}
.mode:has(input:checked) .mode-swatch::after { transform: scaleX(0); opacity: 0; }
.mode:has(input:checked) .mode-swatch { transform: scale(1.07); }
.mode:hover .mode-swatch::after { opacity: .45; }

/* The chosen card gets one sweep across it, left to right. A CSS
   animation restarts whenever its rule starts matching an element, so
   selecting a card is what fires this - no script needed. */
.mode::after {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  background: linear-gradient(90deg,
    transparent 0%,
    color-mix(in srgb, var(--accent-vivid) 34%, transparent) 42%,
    color-mix(in srgb, var(--accent-vivid) 34%, transparent) 58%,
    transparent 100%);
  translate: -105% 0;
}
.mode:has(input:checked)::after { animation: mode-sweep .62s cubic-bezier(.3,.7,.2,1) 1; }
@keyframes mode-sweep {
  from { translate: -105% 0; }
  to   { translate: 105% 0; }
}

.mode-body { position: relative; transition: opacity .3s ease; }
.mode:not(:has(input:checked)) .mode-body { opacity: .68; }
.mode:has(input:checked) {
  border-color: var(--accent-vivid);
  background: color-mix(in srgb, var(--accent-vivid) 11%, var(--surface-2));
}
:root[data-theme="light"] .mode:has(input:checked) {
  box-shadow: 0 8px 22px -14px color-mix(in srgb, var(--accent-vivid) 80%, transparent);
}

@media (max-width: 640px) {
  .hero { padding: 3.2rem 0 2.4rem; }
  .pf-cols { grid-template-columns: repeat(2, 1fr); gap: 2rem; }
  .pf-col:nth-child(3) { display: none; }
  .pf-bar { height: 11px; }
}

@media (prefers-reduced-motion: reduce) {
  /* The global rule above already strips animation. Restated so the
     intent survives if these blocks are ever reordered. */
  .pf-bar { animation: none; transform: scaleX(1); }
  .mode::after { display: none; }
}


/* ==========================================================
   SURFACE PASS
   ==========================================================
   Everything below is presentation only - no layout is moved
   and no element is added that carries meaning. The aim is to
   make flat fills read as lit material rather than as areas
   of colour, which is most of the difference between a page
   that looks built and one that looks unstyled.
   ========================================================== */

/* ---------- 1. panels, lit from above ----------
   A 1px inset highlight on the top edge, a gradient that is
   fractionally lighter where the light would fall, and a
   shadow underneath. Three cheap cues, and together they do
   more than any amount of border work. */
.panel {
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--text) 3.5%, var(--surface)) 0%,
      var(--surface) 42%);
  box-shadow:
    var(--shadow-md),
    inset 0 1px 0 color-mix(in srgb, var(--text) 9%, transparent);
}
:root[data-theme="light"] .panel {
  background:
    linear-gradient(180deg, #FFFFFF 0%,
      color-mix(in srgb, var(--surface) 88%, #FFFFFF) 100%);
  box-shadow:
    0 1px 2px rgba(24, 26, 42, .04),
    0 10px 26px -18px rgba(24, 26, 42, .18),
    inset 0 1px 0 #FFFFFF;
}

/* ---------- 2. grain ----------
   Flat dark fills look like plastic. A very fine noise layer
   over everything reads as material instead. body::after is
   already the light-mode horizon, so this takes ::before.
   Fixed and non-interactive, so it costs one composited layer
   and nothing else. */
body::before {
  content: "";
  position: fixed;
  inset: 0;
  z-index: 3;
  pointer-events: none;
  opacity: .035;
  background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='140' height='140'%3E%3Cfilter id='n'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='.85' numOctaves='3'/%3E%3C/filter%3E%3Crect width='140' height='140' filter='url(%23n)'/%3E%3C/svg%3E");
}
:root[data-theme="light"] body::before { opacity: .022; }

/* ---------- 3. the dropzone ----------
   The biggest element on every tool page, and it was a dashed
   rectangle with two lines of text in it. Now: a ghosted
   outline mark behind the words, faint graph paper inside, and
   the tool's own accent flooding in on dragover. */
.drop {
  position: relative;
  overflow: hidden;
  padding: 3rem 1.4rem;
  background:
    linear-gradient(180deg,
      color-mix(in srgb, var(--text) 2%, var(--surface-2)),
      var(--surface-2));
  transition: border-color .2s, background .2s, box-shadow .25s;
}
/* graph paper */
.drop::before {
  content: "";
  position: absolute;
  inset: 0;
  pointer-events: none;
  opacity: .5;
  background-image:
    linear-gradient(color-mix(in srgb, var(--text) 4%, transparent) 1px, transparent 1px),
    linear-gradient(90deg, color-mix(in srgb, var(--text) 4%, transparent) 1px, transparent 1px);
  background-size: 26px 26px;
  -webkit-mask-image: radial-gradient(70% 80% at 50% 50%, #000, transparent 78%);
          mask-image: radial-gradient(70% 80% at 50% 50%, #000, transparent 78%);
}
/* the ghost mark: a stack of redaction bars, the logo at room scale */
.drop::after {
  content: "";
  position: absolute;
  left: 50%;
  top: 50%;
  width: 190px;
  height: 92px;
  translate: -50% -50%;
  pointer-events: none;
  opacity: .09;
  background:
    linear-gradient(var(--text) 0 0) no-repeat 0 0 / 100% 14px,
    linear-gradient(var(--text) 0 0) no-repeat 0 26px / 74% 14px,
    linear-gradient(var(--text) 0 0) no-repeat 0 52px / 88% 14px,
    linear-gradient(var(--text) 0 0) no-repeat 0 78px / 46% 14px;
  transition: opacity .25s, scale .3s cubic-bezier(.2,.8,.2,1);
}
.drop > * { position: relative; z-index: 1; }
.drop:hover::after { opacity: .14; scale: 1.03; }
.drop.over {
  border-style: dashed;
  border-color: var(--accent);
  background: color-mix(in srgb, var(--accent) 11%, var(--surface-2));
  box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--accent) 30%, transparent),
              0 0 34px -12px color-mix(in srgb, var(--accent-vivid) 55%, transparent);
}
.drop.over::after { opacity: .2; scale: 1.06; }

/* ---------- 4. swatches on checkerboard ----------
   Black box, Blur and Emoji were three grey squares. Against a
   transparency checkerboard the black box reads as COVERING
   something rather than as a shape, which is the whole point of
   the control. */
.mode-swatch {
  background-image:
    linear-gradient(45deg, color-mix(in srgb, var(--text) 7%, transparent) 25%, transparent 25% 75%,
      color-mix(in srgb, var(--text) 7%, transparent) 75%),
    linear-gradient(45deg, color-mix(in srgb, var(--text) 7%, transparent) 25%, transparent 25% 75%,
      color-mix(in srgb, var(--text) 7%, transparent) 75%);
  background-size: 12px 12px;
  background-position: 0 0, 6px 6px;
}
/* ---------- 5. section labels ----------
   "Which faces?" was small grey text. Mono, uppercase, tracked,
   with a hairline running out to the edge - the cheapest way to
   make a form look laid out rather than stacked. */
label.field > span {
  display: flex;
  align-items: center;
  gap: .7rem;
  font-family: var(--mono);
  font-size: .7rem;
  letter-spacing: .1em;
  text-transform: uppercase;
  color: var(--text-faint);
  margin-bottom: .6rem;
}
label.field > span::after {
  content: "";
  flex: 1;
  height: 1px;
  background: linear-gradient(90deg, var(--line), transparent);
}
/* the checkbox rows put their label in a span too, and a rule
   through the middle of a sentence looks like a mistake */
label.field[style*="flex"] > span,
label.field > span[style*="margin:0"] {
  display: inline;
  font-family: inherit;
  font-size: .88rem;
  letter-spacing: normal;
  text-transform: none;
  color: var(--text-dim);
}
label.field[style*="flex"] > span::after,
label.field > span[style*="margin:0"]::after { content: none; }

/* ---------- 6. the result, as a number ----------
   The count is the outcome of the whole flow and it was set as
   a log line. .status-big is added by the script alongside the
   existing text, so nothing breaks if it is not. */
.status-big {
  display: flex;
  align-items: baseline;
  gap: .55rem;
  margin-top: .2rem;
}
.status-big b {
  font-family: var(--display);
  font-size: 2.4rem;
  line-height: 1;
  font-weight: 600;
  letter-spacing: -.02em;
  color: var(--accent-vivid);
}
.status-big span { font-size: .82rem; color: var(--text-dim); }

/* Severity, because a warning set in the same faint grey as a
   success message is not a warning. */
.status.warn {
  color: var(--text);
  font-weight: 500;
  border-left: 3px solid #F59E0B;
  background: color-mix(in srgb, #F59E0B 10%, transparent);
  border-radius: 0 var(--r-sm) var(--r-sm) 0;
  padding: .7rem .9rem;
}
.status.warn .status-big b { color: #F59E0B; }

/* ---------- 7. chips as little redactions ---------- */
.chip {
  display: inline-flex;
  align-items: center;
  gap: .45rem;
}
.chip::before {
  content: "";
  width: 13px;
  height: 8px;
  border-radius: 2px;
  background: var(--text-faint);
  flex: none;
}

/* ---------- 8. the header ----------
   Frosted, with the rule appearing only once there is content
   scrolled under it, and the active tab sitting on a pill of
   the tool's own accent. */
.topbar {
  position: sticky;
  top: 0;
  z-index: 4;
  border-bottom: 1px solid transparent;
  background: color-mix(in srgb, var(--ground) 72%, transparent);
  -webkit-backdrop-filter: blur(14px) saturate(140%);
          backdrop-filter: blur(14px) saturate(140%);
  transition: border-color .25s, background .25s;
}
.topbar.scrolled {
  border-bottom-color: var(--line);
  background: color-mix(in srgb, var(--ground) 86%, transparent);
}
.toolnav a {
  position: relative;
  transition: color .18s, background .18s, border-color .18s;
}
.toolnav a.on {
  color: var(--text);
  background: color-mix(in srgb, var(--accent-vivid) 16%, var(--surface));
  border-color: color-mix(in srgb, var(--accent-vivid) 34%, transparent);
  box-shadow: 0 0 18px -8px color-mix(in srgb, var(--accent-vivid) 60%, transparent);
}
/* Per-tab accent, so the pill is amber on Documents, cyan on
   Photos and violet on Video wherever you are. */
.toolnav a[data-t="file"].on  { --accent-vivid: var(--file-vivid); }
.toolnav a[data-t="image"].on { --accent-vivid: var(--image-vivid); }
.toolnav a[data-t="video"].on { --accent-vivid: var(--video-vivid); }

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


/* ==========================================================
   LIT EDGES
   ==========================================================
   A bright gradient rim with the same colours blooming out
   behind it. Two separate pieces of work, and it matters
   which does which:

   The RIM is a gradient painted into a 1.4px ring, cut out
   with mask-composite. That is the only way to get a gradient
   border - `border-image` cannot follow a border-radius.

   The BLOOM is box-shadow, not a blurred pseudo-element.
   .tool-card is overflow:hidden, so a pseudo reaching past
   the edge would be clipped away, while box-shadow paints
   outside the border box regardless. Offsetting three
   coloured shadows in different directions gives the bloom a
   direction, which is what stops it looking like a uniform
   halo stuck on behind.

   Each surface carries its own pair of hues, so the tool you
   are in is legible from the edge of the page.
   ========================================================== */

:root {
  --g1: #FFB020;   /* amber   */
  --g2: #FF7A45;   /* coral   */
  --g3: #FFCF5C;   /* wheat   */
}
html[data-tool="image"] { --g1: #2DE2F5; --g2: #3B82F6; --g3: #67E8F9; }
html[data-tool="video"] { --g1: #CE9BFF; --g2: #F472B6; --g3: #A78BFA; }

.tool-card[data-tool="file"]  { --g1: #FFB020; --g2: #FF7A45; --g3: #FFCF5C; }
.tool-card[data-tool="image"] { --g1: #2DE2F5; --g2: #3B82F6; --g3: #67E8F9; }
.tool-card[data-tool="video"] { --g1: #CE9BFF; --g2: #F472B6; --g3: #A78BFA; }

.tool-card::after,
.panel::before {
  content: "";
  position: absolute;
  inset: 0;
  border-radius: inherit;
  padding: 1.9px;
  background: linear-gradient(145deg, var(--g1), var(--g2) 46%, var(--g3));
  -webkit-mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
          mask: linear-gradient(#000 0 0) content-box, linear-gradient(#000 0 0);
  -webkit-mask-composite: xor;
          mask-composite: exclude;
  pointer-events: none;
  z-index: 2;
  opacity: .3;
  transition: opacity .22s ease;
}

.panel { position: relative; }

/* The bloom reads as light coming OFF the card, so most of it has to
   land outside the box. That is spread, not blur: a big blur with a big
   negative spread pulls the whole thing back under the card and looks
   like a shadow. These sit near zero spread and let the blur carry it
   out, with one very wide low layer for the far falloff. */
/* Five layers, and each is doing a different job. A tight bright one
   hugging the rim so the edge itself looks hot; two offset ones giving
   the light a direction; a mid layer; and a very wide low one for the
   far falloff. Positive spread on the middle three is what puts the
   colour OUTSIDE the card instead of under it. */
.tool-card {
  border-color: color-mix(in srgb, var(--g2) 20%, transparent);
  box-shadow:
    0 0 20px 1px color-mix(in srgb, var(--g2) 21%, transparent),
    -12px -9px 50px 5px color-mix(in srgb, var(--g1) 22%, transparent),
     12px 10px 50px 5px color-mix(in srgb, var(--g3) 22%, transparent),
     0 0 96px 2px color-mix(in srgb, var(--g2) 17%, transparent),
     0 0 190px -16px color-mix(in srgb, var(--g2) 13%, transparent);
}
.tool-card:hover {
  border-color: color-mix(in srgb, var(--g2) 30%, transparent);
  box-shadow:
    0 0 26px 2px color-mix(in srgb, var(--g2) 26%, transparent),
    -14px -11px 62px 9px color-mix(in srgb, var(--g1) 27%, transparent),
     14px 12px 62px 9px color-mix(in srgb, var(--g3) 27%, transparent),
     0 0 120px 6px color-mix(in srgb, var(--g2) 20%, transparent),
     0 0 230px -10px color-mix(in srgb, var(--g2) 16%, transparent);
}
.tool-card:hover::after { opacity: 1; }

/* The panel is many times the area of a card, so the same bloom at the
   same strength would wash the page out. Rim at full strength, bloom at
   roughly half. */
.panel {
  border-color: color-mix(in srgb, var(--g2) 16%, transparent);
  box-shadow:
    0 0 22px 0 color-mix(in srgb, var(--g2) 15%, transparent),
    -22px -15px 80px 2px color-mix(in srgb, var(--g1) 15%, transparent),
     22px 17px 80px 2px color-mix(in srgb, var(--g3) 15%, transparent),
     0 0 150px 0 color-mix(in srgb, var(--g2) 13%, transparent),
     0 0 280px -30px color-mix(in srgb, var(--g2) 10%, transparent),
     inset 0 1px 0 color-mix(in srgb, var(--text) 9%, transparent);
}
.panel::before { opacity: .23; }

/* Light mode gets the same idea at a fraction of the strength. A neon
   bloom on a white page reads as a printing fault, but dropping it
   entirely would lose the per-tool colour coding, so the rim stays and
   the bloom becomes a tinted shadow. */
:root[data-theme="light"] .tool-card::after { opacity: .55; }
:root[data-theme="light"] .panel::before    { opacity: .4; }
:root[data-theme="light"] .tool-card {
  box-shadow:
    0 10px 30px -20px color-mix(in srgb, var(--g2) 70%, transparent),
    var(--shadow-sm);
}
:root[data-theme="light"] .tool-card:hover {
  box-shadow:
    0 14px 38px -18px color-mix(in srgb, var(--g2) 85%, transparent),
    var(--shadow-md);
}
:root[data-theme="light"] .panel {
  box-shadow:
    0 14px 44px -34px color-mix(in srgb, var(--g2) 70%, transparent),
    var(--shadow-md);
}

/* Card content sits above the rim; the rim is at z-index 2 so that it
   draws over the radial haze inside the card. */
.tool-card > * { position: relative; z-index: 3; }
