:root {
    /* Brand tokens, not the old cool greys. --bg was #f4f4f2, which read
       grey against the rest of the site. The surface stays near-white so
       the drop zone and cards keep their contrast against the cream page. */
    --bg: var(--color-cream);
    --surface: #fffcf7;
    --ink: var(--color-cocoa);
    --muted: #6b6259;
    --faint: #8a7f74;
    /* --line only ever edges non-interactive cards and table rows, so it has
       no contrast minimum to meet. --line-strong draws control boundaries
       (the drop zone, the details disclosure, the Download button) and was
       #dbcab8, which measured 1.48:1 on cream and 1.56:1 on the surface,
       failing WCAG 1.4.11. Mocha is 4.18:1 on cream and stays in palette. */
    --line: #ece0d4;
    --line-strong: var(--color-mocha);
    --good: #0f7b4f;
    --good-bg: #eaf6ef;
    /* 8.80:1 on --warn-bg. Was #b4341f with no consumer at all while
       .banner.warn hardcoded the same value it now reads from here. */
    --warn: #7a2314;
    --warn-bg: #fbecea;
    --radius: 14px;
    /* House monospace stack. The old one named 'JetBrains Mono', which
       branding.md reserves for one page and this one never loads, so anyone
       with it installed locally saw a different metadata table. */
    --mono: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace;
}

* { box-sizing: border-box; }

html, body { background: var(--bg); }
body {
    margin: 0;
    color: var(--ink);
    min-height: 100vh;
    display: flex;
    flex-direction: column;
    -webkit-font-smoothing: antialiased;
    accent-color: var(--ink);
}
[v-cloak] { display: none; }

.sr-only {
    position: absolute; width: 1px; height: 1px; padding: 0; margin: -1px;
    overflow: hidden; clip: rect(0,0,0,0); white-space: nowrap; border: 0;
}

a.skip {
    position: absolute; left: -9999px; top: 0;
    background: var(--ink); color: #fff; padding: 0.6rem 1rem; z-index: 50;
    border-radius: 0 0 8px 0;
}
a.skip:focus { left: 0; }

.wrap {
    width: 100%;
    max-width: 620px;
    margin: 0 auto;
    padding: 2rem 1.25rem 4rem;
    flex: 1;
}

/* The doodles are absolutely positioned against this block. */
header#tool { position: relative; }

/* No font-size here on purpose: the h1 carries .brand-heading with Tailwind's
   text-3xl / sm:text-4xl, and an unlayered page rule would beat the utility. */
h1 {
    line-height: 1.25;
    margin: 0 0 0.6rem;
}
.lede {
    color: var(--muted);
    font-size: 1rem;
    line-height: 1.55;
    margin: 0 0 2.25rem;
    max-width: 52ch;
}

h2.step-label {
    font-size: 0.78rem;
    font-weight: 700;
    text-transform: uppercase;
    letter-spacing: 0.07em;
    color: var(--muted);
    margin: 0 0 0.8rem;
}
section.step { margin-bottom: 2.25rem; }

/* Drop zone */
.drop {
    display: block;
    border: 1.5px dashed var(--line-strong);
    border-radius: var(--radius);
    background: var(--surface);
    padding: 2rem 1.25rem;
    text-align: center;
    cursor: pointer;
    transition: border-color 0.15s, background 0.15s;
}
/* .drag is the drag-over state, not a hover state, so it stays unconditional.
   The hover half is gated the way daisyUI gates its own, or a touch device
   keeps the hover fill after the tap that opened the file picker. */
.drop.drag { border-color: var(--ink); background: #faf5ee; }
@media (hover: hover) {
    .drop:hover { border-color: var(--ink); background: #faf5ee; }
}
.drop:focus-within { outline: 2px solid var(--ink); outline-offset: 2px; }
.drop .ico { color: var(--faint); margin-bottom: 0.6rem; }
.drop .main { font-weight: 600; font-size: 0.95rem; }
.drop .sub { color: var(--muted); font-size: 0.85rem; margin-top: 0.25rem; }

/* Teaser (empty state) */
.teaser { list-style: none; margin: 1.1rem 0 0; padding: 0; }
.teaser li {
    display: flex; align-items: flex-start; gap: 0.55rem;
    font-size: 0.88rem; color: var(--muted); line-height: 1.5;
    margin-bottom: 0.45rem;
}
.teaser svg { flex-shrink: 0; margin-top: 2px; color: var(--faint); }
.teaser-cap { font-size: 0.85rem; color: var(--muted); margin: 0.7rem 0 0; }

/* Thumb row */
.thumb-row {
    display: flex; align-items: center; gap: 0.9rem;
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 0.75rem;
}
.thumb {
    width: 56px; height: 56px; border-radius: 9px;
    object-fit: cover; background: #f3ece2; flex-shrink: 0;
    border: 1px solid var(--line);
}
.thumb-meta { flex: 1; min-width: 0; }
.thumb-meta .name { font-weight: 600; font-size: 0.9rem; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.thumb-meta .dim { color: var(--muted); font-size: 0.82rem; }

/* Verdict card */
.result {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    padding: 1.3rem 1.4rem;
}
.verdict-head { font-size: 1rem; font-weight: 650; margin: 0 0 0.9rem; line-height: 1.5; }
ul.verdict { list-style: none; margin: 0; padding: 0; }
ul.verdict li {
    display: flex; align-items: flex-start; gap: 0.6rem;
    font-size: 0.95rem; line-height: 1.55;
    padding: 0.5rem 0;
    border-top: 1px dashed var(--line);
}
ul.verdict li:first-child { border-top: 0; padding-top: 0; }
ul.verdict svg { flex-shrink: 0; margin-top: 3px; color: var(--ink); }
/* The highest-risk finding, the one that can point at somebody's front door,
   gets the AA-safe coral. 4.70:1 on cream, 4.79:1 on the card surface. */
ul.verdict li.is-high > svg { color: var(--color-coral-deep); }
ul.verdict .risk-tag { margin-left: 0.4rem; }
ul.verdict .sub { display: block; font-size: 0.82rem; color: var(--muted); margin-top: 0.15rem; }
/* Tailwind preflight strips link underlines, and the anchor inherited the exact
   colour of the sentence around it, so the map link measured 1.00:1 against its
   own context with no underline. Colour alone fails WCAG 1.4.1 anyway. */
ul.verdict a {
    color: var(--color-espresso);
    text-decoration: underline;
    text-underline-offset: 2px;
}
ul.verdict a:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; border-radius: 4px; }

/* Details disclosure + table. The disclosure is interactive, so its edge takes
   --line-strong like every other control boundary on the page. */
details.more {
    border: 1px solid var(--line-strong);
    border-radius: var(--radius);
    background: var(--surface);
    padding: 0.25rem 1rem;
    margin-top: 1rem;
}
details.more summary {
    cursor: pointer;
    font-weight: 600;
    font-size: 0.9rem;
    padding: 0.65rem 0.1rem;
    color: var(--ink);
    border-radius: 8px;
}
details.more summary:focus-visible { outline: 2px solid var(--ink); outline-offset: 2px; }
details.more[open] { padding-bottom: 0.9rem; }
h3.group-label {
    font-size: 0.75rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: 0.07em; color: var(--muted); margin: 1.1rem 0 0.3rem;
}
table.meta-table { width: 100%; border-collapse: collapse; }
table.meta-table caption { text-align: left; }
table.meta-table th[scope="row"] {
    text-align: left; font-weight: 600; font-size: 0.85rem;
    padding: 0.4rem 0.75rem 0.4rem 0; vertical-align: top;
    width: 40%; word-break: break-word;
}
table.meta-table td {
    font-family: var(--mono); font-size: 0.8rem; color: var(--muted);
    padding: 0.4rem 0; word-break: break-word; vertical-align: top;
}
table.meta-table tr { border-top: 1px dashed var(--line); }
table.meta-table tr:first-child { border-top: 0; }

/* Buttons are real daisyUI buttons now. Everything the five hand-rolled classes
   used to re-implement (padding, radius, inline-flex centring, gap, transition,
   focus-visible) already ships in the bundle. What is left is sizing, scoped
   through element and cf- selectors, never a bare .btn rule. */
.cf-make { --size: 3rem; --fontsize: 0.98rem; }
/* daisyUI's default button border is a near-white grey that measured about
   1.1:1 against the green card. A control edge needs 3:1 under WCAG 1.4.11.
   The link-style reset button has no boundary to draw, so it is excluded. */
.clean-card .actions > button:not(.btn-link) { --btn-border: var(--color-mocha); }

.clean-card {
    background: var(--good-bg);
    border: 1px solid #cfe8db;
    border-radius: var(--radius);
    padding: 1.3rem 1.4rem;
    overflow-wrap: anywhere; /* long camera filenames must wrap, not overflow */
}
.clean-card .headline {
    display: flex; align-items: center; gap: 0.55rem;
    font-weight: 650; font-size: 1rem; margin: 0 0 0.5rem; color: #143d2b;
}
.clean-card .headline svg { color: var(--good); flex-shrink: 0; }
.clean-card .file-line { font-size: 0.88rem; color: #2c5942; margin: 0 0 1rem; }
.clean-card .actions { display: flex; gap: 0.6rem; flex-wrap: wrap; align-items: center; }

.note {
    display: flex; gap: 0.55rem; align-items: flex-start;
    font-size: 0.85rem; line-height: 1.5; color: var(--muted);
    margin-top: 0.9rem;
}
.note svg { flex-shrink: 0; margin-top: 2px; }

.banner {
    display: flex; gap: 0.55rem; align-items: flex-start;
    border-radius: 10px; padding: 0.75rem 0.9rem;
    font-size: 0.88rem; line-height: 1.45; margin-top: 1rem;
}
.banner.warn { background: var(--warn-bg); color: var(--warn); border: 1px solid #f0ccc5; }
.banner.warn svg { flex-shrink: 0; margin-top: 1px; }

.spin {
    width: 15px; height: 15px; border: 2px solid rgba(255,255,255,0.4);
    border-top-color: #fff; border-radius: 50%;
    animation: spin 0.7s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

.foot {
    text-align: center; padding: 1.5rem 1rem 2rem;
    font-size: 0.8rem; color: var(--muted);
}

@media (prefers-reduced-motion: reduce) {
    .drop, .cf-make, .thumb-row button, .clean-card .actions > button { transition: none; }
    .spin { animation: none; }
}

@media (max-width: 480px) {
    .wrap { padding-top: 1.5rem; }
    /* Thumb-sized targets. "Change" rendered 82x41 and "Check another photo"
       rendered 137x21, under the 24px floor of WCAG 2.5.8 before this. */
    .thumb-row button, .clean-card .actions > button { min-height: 44px; }
}
