/* ==========================================================================
   main.css — hand-written styles for PDFTURA.
   Class names match the view templates exactly (BEM-ish). Layout order per the
   PRD: compact hero -> tools grid -> supporting sections -> footer tool index.
   Mobile-first; the tool grid collapses 4 -> 2 -> 1.
   ========================================================================== */

/* --- Reset / base --------------------------------------------------------- */
*, *::before, *::after { box-sizing: border-box; }

html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; }

body {
    margin: 0;
    font-family: var(--font-sans);
    font-size: var(--fs-base);
    line-height: var(--lh-base);
    color: var(--c-ink);
    background: var(--c-surface-2);
    /* Sticky footer: on short pages (e.g. empty History) the footer used to
       sit mid-window with background showing beneath it. */
    min-height: 100vh;
    display: flex;
    flex-direction: column;
}

main#main { flex: 1 0 auto; }

h1, h2, h3, p { margin: 0 0 var(--s-3); }
h1 { font-size: var(--fs-3xl); line-height: var(--lh-tight); }
h2 { font-size: var(--fs-2xl); line-height: var(--lh-tight); }
h3 { font-size: var(--fs-lg); }

a { color: var(--c-accent); text-decoration: none; }
a:hover { text-decoration: underline; }

img, svg { max-width: 100%; }

.container {
    width: 100%;
    max-width: var(--container);
    margin-inline: auto;
    padding-inline: var(--s-4);
}

.visually-hidden,
.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;
}

.section-title { text-align: center; margin-bottom: var(--s-5); }
.section-lead { text-align: center; color: var(--c-muted); max-width: 60ch; margin: 0 auto var(--s-5); }

/* --- Buttons -------------------------------------------------------------- */
.btn {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
    padding: var(--s-3) var(--s-5);
    border: 1px solid transparent; border-radius: var(--r-md);
    font-size: var(--fs-sm); font-weight: 600; cursor: pointer;
    transition: background .15s, border-color .15s, color .15s, transform .1s;
    text-decoration: none; font-family: inherit;
}
.btn:disabled { opacity: .5; cursor: not-allowed; }
.btn--primary { background: var(--c-accent); color: #fff; }
.btn--primary:hover:not(:disabled) { background: var(--c-accent-dark); text-decoration: none; }
.btn--ghost { background: transparent; border-color: var(--c-line); color: var(--c-ink); }
.btn--ghost:hover:not(:disabled) { background: var(--c-surface-3); text-decoration: none; }
.btn--outline { background: transparent; border-color: var(--c-accent); color: var(--c-accent); }
.btn--outline:hover:not(:disabled) { background: var(--c-accent-soft); text-decoration: none; }
.btn--block { width: 100%; }
.btn--sm { padding: var(--s-2) var(--s-4); font-size: var(--fs-sm); }

/* "Add more" in the tool workspace: a premium soft-accent pill that flips to
   the brand gradient on hover. Distinct, but never louder than the main CTA. */
.btn--addmore {
    background: var(--c-accent-soft); color: var(--c-accent);
    border: 1px solid transparent; border-radius: var(--r-full);
    font-weight: 700; box-shadow: var(--sh-sm);
}
.btn--addmore svg { stroke-width: 2.5; }
.btn--addmore:hover:not(:disabled) {
    background: linear-gradient(120deg, var(--c-accent), #8b3fe0);
    color: #fff; text-decoration: none;
    box-shadow: var(--sh-md); transform: translateY(-1px);
}

/* --- Header --------------------------------------------------------------- */
.site-header {
    position: sticky; top: 0; z-index: 50;
    background: rgba(255, 255, 255, 0.9);
    backdrop-filter: saturate(1.4) blur(8px);
    border-bottom: 1px solid var(--c-line);
}
.site-header__inner {
    display: flex; align-items: center; gap: var(--s-5);
    height: var(--header-h);
}
.brand {
    display: inline-flex; align-items: center; gap: var(--s-2);
    font-weight: 800; font-size: var(--fs-lg); color: var(--c-ink);
    letter-spacing: -0.02em;
}
.brand:hover { text-decoration: none; }
.brand__mark {
    display: inline-flex; align-items: center; line-height: 0;
    flex: none;
}
.brand__mark svg { display: block; border-radius: 9px; }
.brand__name { font-weight: 800; letter-spacing: -0.02em; }
/* Desktop nav: pushed to the right (margin-left:auto). The top-level links are
   styled like the reference site — uppercase, bold, letter-spaced. Scoped to
   direct children so the mega-menu panel below keeps normal (title) casing. */
/* Sits toward the left (a fixed gap after the brand), with the free space
   pushed to the right and wider spacing between links. */
.site-nav { display: flex; align-items: center; gap: var(--s-6); margin-left: var(--s-7); margin-right: auto; }
.site-nav > a {
    color: var(--c-ink); font-size: var(--fs-sm); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.04em;
}
.site-nav > a:hover { color: var(--c-accent); text-decoration: none; }

/* The account cluster no longer needs to claim the right edge (the nav does),
   but keep a small gap from the nav. */
.header-account { margin-left: var(--s-5); }

/* --- Tools mega-menu ------------------------------------------------------ */
/* Static so the full-width panel positions against the (sticky, positioned)
   .site-header, letting it span the container instead of the narrow trigger. */
.mega { position: static; }
.mega__trigger {
    display: inline-flex; align-items: center; gap: 4px;
    border: 0; background: transparent; cursor: pointer;
    color: var(--c-ink); font-size: var(--fs-sm); font-weight: 700;
    text-transform: uppercase; letter-spacing: 0.04em;
    font-family: inherit; padding: 0;
}
.mega__trigger:hover { color: var(--c-accent); }
.mega__caret { transition: transform .18s ease; }
.mega[data-open] .mega__caret { transform: rotate(180deg); }
.mega[data-open] .mega__trigger { color: var(--c-ink); }

/* Full-width panel so every category (all 9, including AI Tools) is visible.
   Pinned to the container edges rather than the trigger, and laid out as a
   responsive grid that never clips columns. */
.mega__panel {
    position: absolute; top: calc(100% + 12px); left: 0; right: 0;
    width: min(var(--container), calc(100vw - var(--s-5) * 2));
    margin-inline: auto;
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-lg);
    padding: var(--s-5); z-index: 60;
    /* Never taller than the space below the header; scroll inside if the full
       set of categories doesn't fit, so no column row gets clipped off-screen. */
    max-height: calc(100vh - var(--header-h) - 24px);
    overflow-y: auto;
}
.mega__panel[hidden] { display: none; }
.mega__grid {
    display: grid;
    grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));
    gap: var(--s-5);
}
.mega__col-title {
    display: flex; align-items: center; gap: var(--s-2);
    color: var(--c-ink); font-weight: 700;
    font-size: 0.875rem; margin-bottom: var(--s-3);
}
.mega__col-title:hover { color: var(--c-accent); text-decoration: none; }
/* Category icon: a small rounded colour tile (same palette as tool cards). */
.mega__col-ic {
    flex: none; width: 26px; height: 26px; border-radius: 8px;
    display: inline-flex; align-items: center; justify-content: center;
}
.mega__links { list-style: none; padding: 0; margin: 0; }
.mega__links li { margin-bottom: 2px; }
.mega__links a {
    display: flex; align-items: center; gap: var(--s-2);
    color: var(--c-ink-soft); font-size: var(--fs-xs); font-weight: 500;
    padding: 3px 0; border-radius: 8px;
    transition: color .12s ease, transform .12s ease;
}
.mega__links a:hover { color: var(--c-accent); text-decoration: none; transform: translateX(2px); }
/* Per-tool icon: a tiny colour tile matching the tool's card colour. */
.mega__tool-ic {
    flex: none; width: 22px; height: 22px; border-radius: 6px;
    display: inline-flex; align-items: center; justify-content: center;
}
/* "All <category> →" aligns with the tool names (icon width + gap). */
.mega__more { color: var(--c-accent) !important; font-weight: 600; padding-left: 30px !important; }

/* Account links / login state in the header. */
.header-account { display: flex; align-items: center; gap: var(--s-3); flex: 0 0 auto; margin-left: auto; }
.header-account__link {
    display: inline-flex; align-items: center; gap: var(--s-2);
    color: var(--c-ink); font-size: var(--fs-sm); font-weight: 600;
}
.header-account__avatar { border-radius: var(--r-full); }
.header-account__logout { color: var(--c-ink-soft); font-size: var(--fs-sm); font-weight: 600; }

/* --- Avatar (shared: header, dashboard, settings) ------------------------- */
.avatar { border-radius: var(--r-full); display: block; flex: none; }
.avatar--photo { object-fit: cover; }

/* --- User menu (avatar + first name + three-dot dropdown) ----------------- */
.usermenu { position: relative; }
.usermenu__trigger {
    display: inline-flex; align-items: center; gap: var(--s-2);
    background: transparent; border: 1px solid transparent; cursor: pointer;
    padding: 4px 6px; border-radius: var(--r-full);
    font: inherit; color: var(--c-ink);
}
.usermenu__trigger:hover { background: var(--c-surface-3); }
.usermenu__name { font-size: var(--fs-sm); font-weight: 600; }
.usermenu__dots { display: none; color: var(--c-ink-soft); }
.usermenu__grid { display: none; color: var(--c-ink-soft); }

/* Square icon button (mobile hamburger + profile grid). Hidden on desktop. */
.iconbtn {
    display: none; align-items: center; justify-content: center;
    width: 42px; height: 42px; padding: 0;
    background: transparent; border: 1px solid var(--c-line);
    border-radius: var(--r-md); cursor: pointer; color: var(--c-ink);
}
.iconbtn:hover { background: var(--c-surface-3); }
/* The mobile nav (hamburger) menu wrapper is hidden on desktop. */
.navmenu { display: none; }
/* Left-anchored dropdown (the hamburger panel opens under the left edge). */
.usermenu__panel--left { right: auto; left: 0; }
.usermenu__panel--left::before { right: auto; left: 18px; }
.usermenu__panel {
    position: absolute; top: calc(100% + 10px); right: 0;
    min-width: 248px; background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-lg);
    padding: var(--s-2); z-index: 60; display: grid; gap: 2px;
    /* The panel is shrink-to-fit and anchored `right: 0`, so it grows LEFTWARD
       off the screen. A long name/email inflates its min-content width, and on a
       320px phone the left edge lands off-viewport where it cannot be reached
       (a page does not scroll left of its origin). Cap it to the viewport. */
    max-width: calc(100vw - var(--s-4) * 2);
    min-width: min(248px, calc(100vw - var(--s-4) * 2));
}
.usermenu__panel[hidden] { display: none; }
/* Little caret/pointer joining the panel to its trigger, like the reference. */
.usermenu__panel::before {
    content: ""; position: absolute; top: -6px; right: 18px;
    width: 11px; height: 11px; background: #fff;
    border-left: 1px solid var(--c-line); border-top: 1px solid var(--c-line);
    transform: rotate(45deg);
}
.usermenu__panel a {
    display: flex; align-items: center; gap: var(--s-3);
    padding: var(--s-2) var(--s-3); border-radius: var(--r-md);
    font-size: var(--fs-sm); font-weight: 500; color: var(--c-ink);
}
.usermenu__panel a:hover { background: var(--c-accent-soft); color: var(--c-accent); text-decoration: none; }
/* Menu-item icons: muted by default, tint to accent on row hover. */
.usermenu__ic { flex: none; color: var(--c-muted); transition: color .12s; }
.usermenu__panel a:hover .usermenu__ic { color: var(--c-accent); }
.usermenu__sep { height: 1px; background: var(--c-line); margin: var(--s-1) 0; }

/* Profile block at the top of the logged-in dropdown. */
.usermenu__profile {
    display: flex; align-items: center; gap: var(--s-3);
    padding: var(--s-2) var(--s-3) var(--s-3);
}
.usermenu__profile-text { display: grid; gap: 1px; min-width: 0; }
.usermenu__profile-name { font-weight: 700; color: var(--c-ink); font-size: var(--fs-sm); overflow-wrap: anywhere; }
.usermenu__profile-email {
    color: var(--c-muted); font-size: var(--fs-xs, 0.78rem);
    white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
    /* Needed alongside the parent's min-width:0 — a nowrap span still reports
       the whole string as its min-content contribution to the grid track. */
    min-width: 0;
}

/* Guest dropdown auth buttons (Log in outlined + Sign up filled), stacked. */
.usermenu__auth { display: grid; gap: var(--s-2); padding: var(--s-2) var(--s-2) var(--s-1); }
.usermenu__auth .btn { width: 100%; }

/* The mobile-only category separator is hidden on desktop (logged-in menu). */
.usermenu__sep--mobile { display: none; }
/* The guest three-dot menu and the mobile-only category links are hidden on
   desktop; the responsive block reveals them on small screens. */
.usermenu--guest { display: none; }
.usermenu__mobilenav { display: none; }

/* --- Avatar picker (settings) --------------------------------------------- */
.avatar-current { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-3); }
.avatar-picker { margin-bottom: var(--s-5); }
.avatar-choices { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-3); }
.avatar-choice {
    position: relative; cursor: pointer; border-radius: var(--r-full);
    padding: 3px; border: 2px solid transparent; line-height: 0;
}
.avatar-choice:hover { border-color: var(--c-line); }
.avatar-choice.is-selected { border-color: var(--c-accent); }
.avatar-choice input { position: absolute; opacity: 0; width: 0; height: 0; }
.avatar-choice:focus-within { border-color: var(--c-accent); }

/* --- Compact hero (<= 30vh) ---------------------------------------------- */
.hero {
    position: relative;
    padding: var(--s-7) 0 var(--s-6);
    text-align: center;
    overflow: hidden;
    /* Layered soft glows: the brand indigo up top, plus warm rose + pink
       auras drifting in from the sides for a premium, lit-from-within feel. */
    background:
        radial-gradient(680px 300px at 18% -10%, rgba(225, 29, 99, 0.10), transparent 70%),
        radial-gradient(620px 280px at 84% 0%, rgba(219, 39, 119, 0.10), transparent 72%),
        radial-gradient(1200px 260px at 50% -60px, var(--c-accent-soft), transparent 70%),
        linear-gradient(180deg, #fff, var(--c-surface-2));
}
/* A faint, blurred pink orb that sits behind the headline for extra depth,
   without touching the layout (pointer-events off, decorative only). */
.hero::before {
    content: ""; position: absolute; z-index: 0;
    top: -120px; left: 50%; width: 520px; height: 320px;
    transform: translateX(-50%);
    background: radial-gradient(closest-side, rgba(244, 114, 182, 0.18), transparent 70%);
    filter: blur(24px); pointer-events: none;
}
.hero .container { position: relative; z-index: 1; }
.hero__title {
    font-size: clamp(1.8rem, 3.4vw, var(--fs-3xl));
    margin-bottom: var(--s-2);
    letter-spacing: -0.025em;
    /* "Free. Private." picks up a subtle indigo→pink gradient via a span. */
}
.hero__title .accent {
    background: linear-gradient(100deg, var(--c-accent) 10%, #db2777 90%);
    -webkit-background-clip: text; background-clip: text;
    -webkit-text-fill-color: transparent; color: transparent;
}
.hero__subtitle { color: var(--c-ink-soft); margin: 0 auto var(--s-5); max-width: 52ch; font-size: var(--fs-lg); }
.hero__search { display: flex; max-width: 580px; margin: 0 auto; }
.hero__search-input {
    flex: 1; padding: var(--s-4) var(--s-5);
    border: 1px solid var(--c-line); border-radius: var(--r-full);
    font-size: var(--fs-base); outline: none; background: #fff;
    box-shadow: var(--sh-md), 0 8px 30px rgba(225, 29, 99, 0.06);
    transition: border-color .15s, box-shadow .15s, transform .15s;
}
.hero__search-input::placeholder { color: var(--c-muted); }
.hero__search-input:focus {
    border-color: var(--c-accent);
    box-shadow: 0 0 0 4px var(--c-accent-soft), 0 10px 34px rgba(91, 75, 255, 0.12);
}

/* --- Tools section + filters --------------------------------------------- */
.tools { padding: var(--s-6) 0; }
.tools__filters {
    display: flex; flex-wrap: wrap; gap: var(--s-2);
    justify-content: center; margin-bottom: var(--s-5);
}
.chip {
    padding: var(--s-2) var(--s-4); border-radius: var(--r-full);
    border: 1px solid var(--c-line); background: #fff;
    font-size: var(--fs-sm); font-weight: 600; color: var(--c-ink-soft);
    cursor: pointer; font-family: inherit;
}
.chip:hover { border-color: var(--c-accent); }
.chip--active { background: var(--c-accent); color: #fff; border-color: var(--c-accent); }
.tools__empty { text-align: center; color: var(--c-muted); padding: var(--s-5) 0; }

/* --- Category groups (used on category hub pages) ------------------------- */
.tool-group { margin-bottom: var(--s-6); }
.tool-group > h2 { margin-bottom: var(--s-1); }
.tool-group__desc { color: var(--c-muted); font-size: var(--fs-sm); margin-bottom: var(--s-4); }

/* --- Tool grid (one flat, full-width grid) -------------------------------- */
.tool-grid {
    display: grid; gap: var(--s-4);
    grid-template-columns: repeat(5, 1fr);
    list-style: none; padding: 0; margin: 0;
}
/* On category pages each card sits in an <li>; stretch both so every card in a
   row is the same height regardless of description length. */
.tool-grid > li { display: flex; }
.tool-grid > li > .tool-card { width: 100%; height: 100%; }
.tool-card {
    position: relative;
    display: flex; flex-direction: column; gap: var(--s-2);
    padding: var(--s-5) var(--s-4);
    background: var(--c-surface); border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-sm);
    transition: transform .12s, box-shadow .12s, border-color .12s;
}
.tool-card:hover {
    transform: translateY(-2px); box-shadow: var(--sh-md);
    border-color: var(--c-accent); text-decoration: none;
}
/* Filtered-out cards (search + chips). Uses a class so we never clobber the
   grid's own display value. */
.tool-card.is-hidden { display: none; }
.tool-card__icon {
    width: 44px; height: 44px; border-radius: var(--r-md);
    display: grid; place-items: center;
    background: var(--c-accent-soft); color: var(--c-accent);
    margin-bottom: var(--s-1);
}
/* Per-tool colour tiles. Each .ico--{name} sets a soft tinted background with a
   matching saturated glyph colour, so every card reads distinctly (like the
   reference sites) instead of one flat purple everywhere. The class lives on
   the icon tile; if a tool has no colour mapping it falls back to accent. */
.ico--red    { background: #fdeaea; color: #e02f2f; }
.ico--orange { background: #fdefe0; color: #e07b1a; }
.ico--amber  { background: #fdf6da; color: #c79008; }
.ico--green  { background: #e4f7ec; color: #17924b; }
.ico--teal   { background: #ddf5f4; color: #0ea5a4; }
.ico--sky    { background: #e3f1fd; color: #1f81d6; }
.ico--blue   { background: #e6ecfe; color: #2f57e0; }
.ico--indigo { background: #eceafe; color: #5b4bff; }
.ico--violet { background: #f2e9fd; color: #8b3fe0; }
.ico--pink   { background: #fce7f3; color: #db2777; }
.ico--rose   { background: #fde7ec; color: #d6336c; }
.ico--slate  { background: #eaeef4; color: #4a6079; }
.tool-card__name { font-weight: 700; color: var(--c-ink); font-size: var(--fs-lg); line-height: 1.25; }
.tool-card__desc {
    color: var(--c-muted); font-size: var(--fs-sm); line-height: 1.5;
    /* Clamp to 3 lines so a long SEO blurb can't stretch a card to 6 lines and
       break the grid's visual rhythm. The full text still lives on the tool
       page; here it's just a teaser. */
    display: -webkit-box; -webkit-box-orient: vertical; -webkit-line-clamp: 3;
    line-clamp: 3; overflow: hidden;
}
.tool-card__badges { position: absolute; top: var(--s-3); right: var(--s-3); display: flex; gap: 4px; }

/* --- Badges --------------------------------------------------------------- */
.badge {
    font-size: 0.65rem; font-weight: 700; text-transform: uppercase;
    letter-spacing: .04em; padding: 2px 6px; border-radius: var(--r-full); color: #fff;
}
.badge--new { background: var(--c-badge-new); }
.badge--beta { background: var(--c-badge-beta); }
.badge--featured { background: var(--c-badge-featured); }
.badge--popular { background: var(--c-badge-popular); }

/* --- "Why" / trust points ------------------------------------------------- */
.why { padding: var(--s-6) 0; }
.why-grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(4, 1fr); }
.why-card {
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); padding: var(--s-4); text-align: center;
}
.why-card h3 { margin-bottom: var(--s-1); }
.why-card p { color: var(--c-muted); font-size: var(--fs-sm); margin: 0; }

/* --- How it works --------------------------------------------------------- */
.how { padding: var(--s-6) 0; }
.how-steps {
    display: grid; gap: var(--s-4); grid-template-columns: repeat(3, 1fr);
    list-style: none; padding: 0; margin: 0; counter-reset: step;
}
.how-steps li {
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); padding: var(--s-4); text-align: center;
}
.how-steps__num {
    display: inline-grid; place-items: center;
    width: 32px; height: 32px; margin-bottom: var(--s-2);
    border-radius: var(--r-full); background: var(--c-accent); color: #fff; font-weight: 700;
}

/* --- Popular / AI highlight ----------------------------------------------- */
.popular, .ai-highlight { padding: var(--s-6) 0; }

/* --- FAQ ------------------------------------------------------------------ */
.faq { padding: var(--s-6) 0; }
.faq .container { max-width: 820px; }
.faq-item, .faq details {
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-md); padding: var(--s-3) var(--s-4); margin-bottom: var(--s-3);
}
.faq-item summary, .faq summary { font-weight: 600; cursor: pointer; }
.faq-item p, .faq p { margin: var(--s-2) 0 0; color: var(--c-ink-soft); }

/* --- Trust bar (stats) ---------------------------------------------------- */
.trust-bar { padding: var(--s-5) 0; background: var(--c-accent-soft); }
.trust-bar__grid {
    display: grid; grid-template-columns: repeat(4, 1fr); gap: var(--s-4);
    text-align: center;
}
.trust-stat { display: flex; flex-direction: column; gap: 2px; }
.trust-stat__num { font-size: var(--fs-2xl); font-weight: 800; color: var(--c-accent); line-height: 1; }
.trust-stat__label { font-size: var(--fs-sm); color: var(--c-ink-soft); font-weight: 600; }

/* --- Use cases ------------------------------------------------------------ */
.usecases { padding: var(--s-6) 0; }
.usecase-grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(4, 1fr); }
.usecase-card {
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); padding: var(--s-5) var(--s-4);
}
.usecase-card h3 { margin-bottom: var(--s-2); }
.usecase-card p { color: var(--c-muted); font-size: var(--fs-sm); margin: 0; }

/* --- Testimonials --------------------------------------------------------- */
.testimonials { padding: var(--s-6) 0; }
.testimonial-grid { display: grid; gap: var(--s-4); grid-template-columns: repeat(3, 1fr); }
.testimonial {
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); padding: var(--s-5); margin: 0;
}
.testimonial blockquote { margin: 0 0 var(--s-3); font-size: var(--fs-lg); line-height: 1.4; color: var(--c-ink); }
.testimonial figcaption { color: var(--c-muted); font-size: var(--fs-sm); font-weight: 600; }

/* --- Final CTA ------------------------------------------------------------ */
.cta { padding: var(--s-6) 0 var(--s-7); }
.cta__box {
    background: linear-gradient(135deg, var(--c-accent), var(--c-accent-dark));
    border-radius: var(--r-lg); padding: var(--s-7) var(--s-5);
    text-align: center; color: #fff;
}
.cta__box h2 { color: #fff; }
.cta__box p { color: rgba(255,255,255,.9); margin-bottom: var(--s-5); }
.cta__box .btn--primary { background: #fff; color: var(--c-accent); border-color: #fff; }
.cta__box .btn--primary:hover { background: rgba(255,255,255,.9); }
.btn--lg { padding: var(--s-3) var(--s-6); font-size: var(--fs-lg); }

/* --- Footer --------------------------------------------------------------- */
.site-footer {
    background: var(--c-ink); color: #c9c9db;
    padding: var(--s-7) 0 var(--s-5); margin-top: var(--s-7);
}
.site-footer a { color: #c9c9db; }
.site-footer a:hover { color: #fff; }
.footer-top {
    display: grid; gap: var(--s-5);
    grid-template-columns: 1.6fr 1fr 1fr 1fr;
    margin-bottom: var(--s-5);
}
.footer-brand__logo {
    display: inline-flex; align-items: center; gap: var(--s-2);
    color: #fff; font-weight: 800; font-size: var(--fs-lg);
}
.footer-brand__logo:hover { text-decoration: none; }
.footer-brand__tag {
    margin: var(--s-3) 0 0; max-width: 32ch;
    font-size: var(--fs-sm); line-height: 1.6; color: #9a9ab0;
}
.footer-col__title {
    color: #fff; font-size: var(--fs-sm); font-weight: 700;
    margin: 0 0 var(--s-3);
}
.footer-links { list-style: none; padding: 0; margin: 0; }
.footer-links li { font-size: var(--fs-sm); }
/* Padding on the anchor, not margin on the li: the link itself must be the
   ~40px tap target. With the mega menu hidden below 900px these are a primary
   navigation path on phones. */
.footer-links a { display: inline-block; padding: var(--s-2) 0; }
.footer-bottom {
    border-top: 1px solid rgba(255,255,255,.12); padding-top: var(--s-4);
    display: flex; justify-content: space-between; flex-wrap: wrap; gap: var(--s-3);
    font-size: var(--fs-sm); color: var(--c-muted);
}
.footer-copy { margin: 0; }
.footer-mail { color: #c9c9db; }

/* Social links under the footer brand blurb. Muted chips that brighten and
   lift to the accent on hover; icons inherit color via currentColor. */
.footer-social {
    list-style: none; padding: 0; margin: var(--s-4) 0 0;
    display: flex; flex-wrap: wrap; gap: var(--s-2);
}
.footer-social__link {
    display: inline-flex; align-items: center; justify-content: center;
    width: 38px; height: 38px; border-radius: var(--r-full);
    color: #c9c9db;
    background: rgba(255, 255, 255, .06);
    border: 1px solid rgba(255, 255, 255, .10);
    transition: color .15s ease, background .15s ease, border-color .15s ease, transform .15s ease;
}
.footer-social__link:hover {
    color: #fff;
    background: var(--c-accent);
    border-color: var(--c-accent);
    transform: translateY(-2px);
}
.footer-social__link svg { display: block; }

/* Minimal footer (account pages): just the copyright bar, so drop the divider
   line and tighten the vertical padding. */
.site-footer--minimal { padding: var(--s-5) 0; }
.site-footer--minimal .footer-bottom { border-top: 0; padding-top: 0; }

/* --- Announcement bar (set from admin Settings) ---------------------------- */
.announce-bar {
    background: linear-gradient(120deg, var(--c-accent), #8b3fe0);
    color: #fff; text-align: center; font-size: 11px; font-weight: 600;
    padding: 4px var(--s-4);
}
.announce-bar__link {
    color: #fff; text-decoration: none;
    display: inline-flex; align-items: center; gap: var(--s-2);
}
.announce-bar__link:hover { text-decoration: underline; }
.announce-bar__cta {
    background: rgba(255,255,255,.2); border-radius: var(--r-full);
    padding: 1px var(--s-3); font-size: 10px; font-weight: 700;
    white-space: nowrap;
}
/* Cycling: only the active slide is visible; others are hidden in place. */
.announce-bar--cycle { position: relative; }
.announce-slide { display: none; }
.announce-slide.is-active { display: block; }

/* --- Contact form ---------------------------------------------------------- */
.contact-form {
    display: grid; gap: var(--s-4);
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-sm);
    padding: var(--s-5); margin-bottom: var(--s-6);
}
.contact-form__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-4); }
.contact-form .opt-field input, .contact-form .opt-field textarea {
    width: 100%; padding: var(--s-3);
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    font: inherit; font-size: var(--fs-sm); font-weight: 400; outline: none;
}
.contact-form .opt-field input:focus, .contact-form .opt-field textarea:focus { border-color: var(--c-accent); }
/* Inline validation: red border on the box + a small message right under it. */
.contact-form .is-invalid,
.contact-form .is-invalid:focus { border-color: #dc2626; }
.field-error { color: #dc2626; font-size: var(--fs-xs); line-height: 1.35; }

/* --- Promotion popup (once per browser session) ---------------------------- */
.promo {
    position: fixed; inset: 0; z-index: 200;
    display: flex; align-items: center; justify-content: center;
    padding: var(--s-4);
}
/* Critical: a class selector's `display:flex` beats the browser's default
   `[hidden]{display:none}`, so without this the overlay stays on screen (just
   invisible) whenever JS sets `hidden` — e.g. on repeat visits where the promo
   cookie is already set. The invisible full-screen layer then swallows every
   click, and its centered image-link navigates to the promo URL. This rule
   makes the `hidden` attribute truly hide (and un-clickable) the popup. */
.promo[hidden] { display: none !important; }
.promo__backdrop {
    position: absolute; inset: 0;
    background: rgba(15, 15, 35, .55);
    backdrop-filter: blur(6px);
    -webkit-backdrop-filter: blur(6px);
    opacity: 0; transition: opacity .22s ease;
}
.promo__card {
    position: relative; width: 100%; max-width: 440px;
    background: #fff; border-radius: 22px; overflow: hidden;
    box-shadow:
        0 1px 2px rgba(15, 15, 35, .10),
        0 12px 28px rgba(15, 15, 35, .22),
        0 32px 72px rgba(15, 15, 35, .30);
    opacity: 0; transform: translateY(14px) scale(.94);
    transition: opacity .22s ease, transform .26s cubic-bezier(.2, .9, .3, 1.15);
}
.promo.is-open .promo__backdrop { opacity: 1; }
.promo.is-open .promo__card { opacity: 1; transform: translateY(0) scale(1); }
.promo__close {
    position: absolute; top: 10px; right: 10px; z-index: 2;
    width: 34px; height: 34px; border: 0; border-radius: 50%;
    background: rgba(255, 255, 255, .92); color: var(--c-ink);
    font-size: 22px; line-height: 1; cursor: pointer;
    display: flex; align-items: center; justify-content: center;
    box-shadow: 0 2px 8px rgba(15, 15, 35, .25);
    transition: transform .15s ease, background .15s ease;
}
.promo__close:hover { transform: rotate(90deg); background: #fff; }
.promo__media { display: block; line-height: 0; }
.promo__media img { width: 100%; height: auto; aspect-ratio: 3 / 2; object-fit: cover; }
.promo__body { padding: var(--s-4) var(--s-5) var(--s-5); text-align: center; }
.promo__title {
    margin: 0 0 var(--s-2); font-size: var(--fs-xl); line-height: var(--lh-tight);
    letter-spacing: -0.01em;
}
.promo__text { margin: 0 0 var(--s-4); color: var(--c-ink-soft); font-size: var(--fs-sm); }
.promo__cta { min-width: 60%; }
@media (max-width: 480px) {
    .promo__card { max-width: 340px; }
    .promo__body { padding: var(--s-4); }
}
.contact-form .opt-field textarea { resize: vertical; min-height: 140px; }
.contact-form__bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--s-3); flex-wrap: wrap;
}
/* Honeypot lives off-screen; display:none would tip off smarter bots. */
.contact-form__hp {
    position: absolute; left: -9999px; top: -9999px;
    height: 1px; width: 1px; overflow: hidden;
}
@media (max-width: 560px) {
    .contact-form__row { grid-template-columns: 1fr; }
}

/* --- Static doc pages (About, Contact, Privacy, Terms, Security) ---------- */
.doc-page { max-width: 760px; margin: 0 auto; padding: var(--s-6) var(--s-4) var(--s-7); }
.doc-head { margin-bottom: var(--s-5); padding-bottom: var(--s-4); border-bottom: 1px solid var(--c-line); }
.doc-head h1 { font-size: var(--fs-2xl); margin: 0 0 var(--s-2); }
.doc-lede { color: var(--c-ink-soft); font-size: var(--fs-lg); margin: 0; }
.doc-updated { color: var(--c-muted); font-size: var(--fs-sm); margin-top: var(--s-2); }
.doc-body h2 { font-size: var(--fs-lg); margin: var(--s-5) 0 var(--s-2); }
.doc-body p, .doc-body li { color: var(--c-ink-soft); line-height: var(--lh-base); }
.doc-body ul { padding-left: var(--s-5); margin: var(--s-3) 0; }
.doc-body li { margin-bottom: var(--s-2); }
.doc-body a { color: var(--c-accent); }

/* --- Breadcrumb ----------------------------------------------------------- */
.breadcrumb {
    max-width: var(--container); margin: 0 auto; padding: var(--s-4) var(--s-4) 0;
    font-size: var(--fs-sm); color: var(--c-muted);
    display: flex; gap: var(--s-2); align-items: center; flex-wrap: wrap;
    /* A long page title is one flex item with min-width:auto, so it cannot
       shrink below its longest word. Allow it to break instead of pushing the
       row wider than a phone. */
    min-width: 0; overflow-wrap: anywhere;
}

/* --- Tool page ------------------------------------------------------------ */
.tool-page { max-width: 1140px; margin: 0 auto; padding: var(--s-3) var(--s-4) var(--s-7);
    display: flex; flex-direction: column; }

/* The single in-content ad. By default (empty state) it sits BELOW the tool;
   once the runtime adds .is-loaded it moves ABOVE the tool. Everything else
   keeps its natural document order (order: 0). The hero (.tool-head) is
   collapsed on load, so "above the tool" reads as the top of the page. */
.tool-page > .ad-slot { order: 3; margin: var(--s-4) auto 0; width: fit-content; max-width: 100%; }
.tool-page.is-loaded > .ad-slot { order: -1; margin: 0 auto var(--s-4); }

/* The FOOTER ad is a second, independent unit and must stay at the bottom.
   Both rules above select ANY .ad-slot child of .tool-page, so without these
   overrides the footer unit would sit at order 3 (above the info/related
   sections at order 4) and then jump to the TOP of the page the moment the
   runtime added .is-loaded. order: 5 puts it after everything.

   Specificity: `.tool-page > .ad-slot` (729) and `.tool-page > .ad-slot--footer`
   are BOTH (0,2,0), and the two `.is-loaded` variants are both (0,3,0) — each
   pair is an exact tie, so SOURCE ORDER is the only thing that makes these win.
   These two rules must stay below the two above or the fix silently reverts. */
.tool-page > .ad-slot--footer,
.tool-page.is-loaded > .ad-slot--footer { order: 5; margin: var(--s-5) auto 0; }
.tool-page > .tool-head { order: 1; }
.tool-page > .tool-workspace,
.tool-page > .tool-app,
.tool-page > .ai-tool,
.tool-page > .util-tool,
.tool-page > .tool-workarea { order: 2; }
.tool-page > .tool-info,
.tool-page > .related-tools { order: 4; }
.tool-head { text-align: center; margin-bottom: var(--s-3); max-width: 720px; margin-left: auto; margin-right: auto; }
.tool-head h1 { font-size: clamp(1.6rem, 4vw, 2.3rem); line-height: 1.15; margin-bottom: var(--s-2); }
.tool-badges { display: flex; gap: 4px; justify-content: center; margin-bottom: var(--s-3); }
.tool-lede { color: var(--c-ink-soft); max-width: 60ch; margin: 0 auto; }

/* Once files are loaded the hero (title + badges + lede) is redundant — the
   sidebar carries the tool name — so we collapse it to reclaim vertical space. */
.tool-page.is-loaded .tool-head { display: none; }

/* The workspace spans the wide container, but the supporting reading content
   (intro, how-to steps, FAQs) stays narrow and centered for legibility.

   width:100% is required — same trap as .util-tool below: .tool-page is a flex
   column, and margin-left/right:auto on a flex item cancels the default
   cross-axis stretch. Without it the section collapses to its content width, so
   the FAQ block was only as wide as the longest collapsed <summary> and visibly
   grew each time a <details> was opened. Tools that happen to define an `intro`
   masked the bug, because the 70ch paragraph pushed the section to the cap. */
.tool-info { max-width: 820px; width: 100%; margin-left: auto; margin-right: auto; }
.tool-workarea { margin-bottom: var(--s-6); }
.tool-info__intro { color: var(--c-ink-soft); max-width: 70ch; margin: var(--s-5) 0 0; line-height: 1.7; }
.tool-info h2 { margin-top: var(--s-6); }
.tool-info .steps { list-style: none; padding: 0; counter-reset: s; }
.tool-info .steps li { padding: var(--s-2) 0; border-bottom: 1px solid var(--c-line); }

/* Opt-in prose sections ("Who it is for", "How it works", "What it cannot do").
   paragraph follows the same measure as .tool-info__intro so the page reads as
   one column rather than alternating widths. */
.tool-info__body { color: var(--c-ink-soft); max-width: 70ch; margin: var(--s-3) 0 0; line-height: 1.7; }
.tool-info__list { max-width: 70ch; margin: var(--s-3) 0 0; padding-left: 1.15em; color: var(--c-ink-soft); line-height: 1.7; }
.tool-info__list li { margin-bottom: var(--s-2); }
.tool-info__list li::marker { color: var(--c-accent, currentColor); }
/* Limits read as a quieter, factual list — no accent markers, so the honest
   caveats never look like marketing bullets. */
.tool-info__list--limits li::marker { color: var(--c-ink-soft); }

/* --- Tool workspace (two-column: canvas + settings sidebar) ---------------
   Class names here match exactly what the tool page (tool.php) and the client
   modules render at runtime. Keep them in sync with runtime.js / the modules.

   Layout: a CSS grid with the file canvas on the left and a settings sidebar on
   the right. The sidebar collapses below the canvas on narrow screens. Tools
   that need a big visual editor add .tool-workspace--wide to stack everything
   into one full-width column. */
.tool-workspace {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr) 340px;
    gap: var(--s-4);
    align-items: start;
    margin-bottom: var(--s-6);
}
.tool-workspace--wide { grid-template-columns: minmax(0, 1fr); }

/* When there are no files yet, the sidebar is hidden and the dropzone should
   span the whole width for a generous drop target. */
.tool-workspace .tool-sidebar[hidden] + noscript,
.tool-workspace:has(.tool-sidebar[hidden]) .tool-canvas { grid-column: 1 / -1; }

/* Empty-state dropzone must be an IDENTICAL size on every tool page (client,
   server, wide-editor) — the workspace grid state used to vary per tool type,
   leaving Merge/Compress/Repair with different-width boxes, and server tools
   (Protect/Repair) render less inner content so they came out shorter too.
   Force the same centered width AND height everywhere. */
.tool-workspace .dropzone,
.tool-workspace--wide .dropzone {
    max-width: 560px; width: 100%; margin-left: auto; margin-right: auto;
    min-height: 200px;
}

.tool-canvas { min-width: 0; display: flex; flex-direction: column; gap: var(--s-4); }

.dropzone {
    border: 2px dashed var(--c-line); border-radius: var(--r-lg);
    background: #fff; padding: var(--s-6) var(--s-5); text-align: center;
    cursor: pointer; transition: border-color .15s, background .15s;
    min-height: 170px; display: flex; flex-direction: column;
    align-items: center; justify-content: center;
    max-width: 640px; margin-left: auto; margin-right: auto;
}
.dropzone--over { border-color: var(--c-accent); background: var(--c-accent-soft); }
/* .dropzone sets display:flex, which would silently defeat the hidden attribute
   (author display beats the UA's [hidden] rule) — that left TWO upload zones on
   screen after a file was added. Force hidden to win. */
.dropzone[hidden] { display: none; }
.dropzone--soon { cursor: default; }
.dropzone:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }
.dropzone__inner { max-width: 520px; margin: 0 auto; }
.dropzone__icon { color: var(--c-accent); margin: 0 auto var(--s-3); }
.dropzone__title { font-size: var(--fs-lg); font-weight: 700; margin-bottom: var(--s-1); }
.dropzone__hint { color: var(--c-muted); font-size: var(--fs-sm); margin-bottom: 0; }

/* Empty-state picker: the prominent "Select PDF file" CTA, sitting above the
   "or drop PDF here" hint, like the reference design. */
.dropzone__select { box-shadow: var(--sh-md); margin-bottom: var(--s-3); }

/* --- "or convert a web page" (HTML to PDF only) ----------------------------
   Injected by html-to-pdf.js directly after the dropzone, so a visitor can
   convert a live URL without picking a file. No other tool renders .url-convert,
   so these rules are inert everywhere else.

   Same trap as .dropzone above: these set display, which would beat the UA's
   [hidden] rule and leave the row on screen after a conversion starts. The
   [hidden] rules below are what make hiding it actually work. */
.url-convert { max-width: 640px; margin: var(--s-4) auto 0; }
.url-convert[hidden] { display: none; }
.url-convert__sep {
    display: flex; align-items: center; gap: var(--s-3);
    color: var(--c-muted); font-size: var(--fs-sm); margin-bottom: var(--s-3);
}
.url-convert__sep[hidden] { display: none; }
.url-convert__sep::before, .url-convert__sep::after {
    content: ""; flex: 1; height: 1px; background: var(--c-line);
}
.url-convert__row { display: flex; gap: var(--s-2); align-items: stretch; }
.url-convert__row[hidden] { display: none; }
.url-convert__input {
    flex: 1; min-width: 0; padding: var(--s-3);
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    font: inherit; font-size: var(--fs-sm); background: #fff;
}
.url-convert__input:focus { border-color: var(--c-accent); outline: none; }
.url-convert__input:disabled { background: var(--c-accent-soft); opacity: .7; }
.url-convert__go { white-space: nowrap; }
.url-convert__hint {
    color: var(--c-muted); font-size: var(--fs-sm);
    margin: var(--s-2) 0 0; text-align: center;
}
@media (max-width: 480px) {
    .url-convert__row { flex-direction: column; }
}

/* --- In-content ad slot ---------------------------------------------------
   A reserved, labelled box for the admin's ad units — in-content and footer.
   Rendered only when ads are enabled and a snippet exists (see layout/_ad.php).
   Note there is NO min-height here: a FIXED-size slot gets its space from the
   explicit height in the mobile ladder below, and on desktop from the creative
   itself. Only `.ad-slot--responsive` reserves space, because that is the one
   case with no declared size at any width. */
.ad-slot {
    /* Declared size of the creative inside. Both ad units in use today are
       Adsterra 728x90 leaderboards, so these are the defaults; the mobile-fit
       ladder further down reads them instead of hard-coding pixels. If a slot
       ever carries a different FIXED format, override these two on that slot.
       They MUST stay lengths with units (`320px`, not `320` or `auto`): the
       ladder feeds them to calc(), and a unitless or keyword value makes the
       whole calc() invalid, dropping width/height back to `auto` and silently
       restoring the very clipping bug the ladder exists to fix.
       For a unit that sizes ITSELF, don't touch these — tick "responsive unit"
       in Admin → Ads and .ad-slot--responsive bypasses the ladder entirely. */
    --ad-w: 728px;
    --ad-h: 90px;
    display: block; text-align: center;
    width: fit-content; max-width: 100%;
    margin-left: auto; margin-right: auto;
    border: 2px solid #9a9ab0;
    padding: 0; font-size: 0;
    overflow: hidden;
}
.ad-slot__label { display: none; }
/* `justify-content: flex-start`, not center: an ad unit wider than the phone
   (a 728x90 leaderboard) would otherwise be clipped on BOTH sides by the
   `overflow: hidden` above, hiding its edges with no way to scroll to them.
   `min-width: 0` lets a flex-item iframe shrink below its own min-content. */
.ad-slot__unit { display: flex; align-items: center; justify-content: flex-start; }
.ad-slot__unit > * { max-width: 100%; min-width: 0; }

/* --- Mobile ad fit --------------------------------------------------------
   The in-content creative is a FIXED-SIZE third-party iframe — Adsterra's
   snippet hard-codes `'width': 728, 'height': 90` and invoke.js builds the
   iframe at exactly that. A fixed iframe cannot reflow, and the `max-width:
   100%` above loses to the inline pixel width the ad script writes, so on a
   ~360px phone only ~48% of the banner sat inside `.ad-slot`'s `overflow:
   hidden` and the rest was clipped away silently.

   Forcing the iframe to `width: 100%` does NOT fix it: the creative inside is
   cross-origin and won't reflow, so you'd just get a narrow window onto the
   same 728px artwork. `transform: scale()` is the only thing that can shrink
   another origin's iframe.

   Because transforms do not affect layout, the BOX must be resized by the same
   factor or a full 728x90 hole stays behind the shrunken ad — hence the paired
   width/height on `.ad-slot` driven by the same `--ad-scale`.

   Discrete steps, not a fluid calc(): a unitless ratio can't be derived from a
   percentage in calc(), and `zoom` (which would size itself) still isn't safe
   on older Firefox. Each step is <= (min viewport in range - 36px chrome) / 728,
   where 36px = .tool-page's 2x16px padding + .ad-slot's 2x2px border.
   NEVER set --ad-scale above 1: upscaling would blur the creative.

   Applies to EVERY .ad-slot, in-content and footer alike — both currently carry
   a 728x90 leaderboard, so both need the same rescue. The scale steps are tuned
   for that 728px width. A slot whose unit sizes itself (AdSense responsive) or
   is already phone-sized (320x50, 300x250) must NOT come through here at all —
   tick "responsive unit" for it in Admin → Ads, which adds .ad-slot--responsive
   and cancels this block further down. */
@media (max-width: 764px) {
    .ad-slot,
    .tool-page > .ad-slot {
        --ad-scale: 0.91;
        /* `+ 4px` because `box-sizing: border-box` (the universal reset on
           line 9) counts .ad-slot's 2px border INSIDE this width — without it
           the content box lands 4px narrower than the scaled unit and
           `overflow: hidden` clips the banner's right edge. */
        width: calc(var(--ad-w) * var(--ad-scale) + 4px);
        height: calc(var(--ad-h) * var(--ad-scale) + 4px);
        max-width: 100%;
    }
    .ad-slot__unit {
        width: var(--ad-w); height: var(--ad-h);
        transform: scale(var(--ad-scale));
        transform-origin: 0 0;
    }
}
@media (max-width: 700px) { .ad-slot, .tool-page > .ad-slot { --ad-scale: 0.83; } }
@media (max-width: 640px) { .ad-slot, .tool-page > .ad-slot { --ad-scale: 0.72; } }
@media (max-width: 560px) { .ad-slot, .tool-page > .ad-slot { --ad-scale: 0.61; } }
@media (max-width: 480px) { .ad-slot, .tool-page > .ad-slot { --ad-scale: 0.50; } }
@media (max-width: 400px) { .ad-slot, .tool-page > .ad-slot { --ad-scale: 0.44; } }
@media (max-width: 360px) { .ad-slot, .tool-page > .ad-slot { --ad-scale: 0.37; } }

/* --- Responsive ad units (opt out of everything above) --------------------
   Set per slot by the "responsive unit" checkbox in Admin → Ads, which adds
   .ad-slot--responsive. Two different creatures need this:
     * a self-sizing unit (Google AdSense `data-ad-format="auto"`), which
       MEASURES its container and requests an ad to match, and
     * a fixed unit that already fits a phone (320x50, 300x250).
   Both are ruined by the ladder: it would report a 728px container to AdSense
   and then shrink whatever came back to 37%, and it would make an already-small
   creative smaller still. So this resets the size and the transform outright.

   Deliberately OUTSIDE the media queries — it has to hold at every width, not
   just on phones, because `.ad-slot`'s desktop defaults break self-sizing units
   too. Four resets, each fixing a specific trap:

   1. `width: 100%`, replacing the `fit-content` that .ad-slot and line 729 both
      set on this element. An AdSense <ins> is an empty block with no intrinsic
      width, so inside a shrink-to-wrap parent it measures 0 and Google serves
      nothing at all. It needs a parent with a real, resolvable width.
   2. `display: block` on the unit, overriding the `display: flex` on line 902.
      As a flex item that same empty <ins> gets `flex: 0 1 auto` and shrink-to-
      fit sizing — 0 width again, same silent blank.
   3. `height: auto`, dropping the ladder's fixed height (90px scaled), which
      would crop a taller responsive creative (a 336x280) to a strip. Note this
      does NOT also set `overflow: visible`: `.ad-slot`'s `overflow: hidden`
      (line 895) must stay. With auto height nothing is cropped vertically — the
      box grows to its content — but `html`/`body` have no `overflow-x: hidden`,
      so an over-wide unit with clipping off would push the WHOLE PAGE sideways
      instead of just spoiling the ad. Clipping is the lesser failure, and it is
      what the Ads admin page promises. `min-height` then reserves the space a
      leaderboard would take so the tool UI doesn't jump when the ad fills late;
      raising it reserves more but leaves a taller empty box when nothing fills.
   4. `font-size` back to a real value: .ad-slot sets `font-size: 0` to kill the
      inline-block gap under a fixed iframe, which would make an AdSense text or
      native ad invisible.

   Every selector below is written to out-specify its ladder counterpart on its
   own — `.ad-slot.ad-slot--responsive` is (0,2,0) against `.ad-slot`'s (0,1,0),
   and so on up. That is deliberate: the doubled class costs nothing and means
   this block does not depend on sitting below the ladder to win, so a size rule
   added after it later can't silently revert responsive slots to the ladder. */
.ad-slot.ad-slot--responsive,
.tool-page > .ad-slot.ad-slot--responsive,
.tool-page.is-loaded > .ad-slot.ad-slot--responsive {
    width: 100%; max-width: 100%; height: auto; min-height: 90px;
}
.ad-slot--responsive .ad-slot__unit {
    display: block; width: auto; height: auto; transform: none;
    font-size: var(--fs-base, 1rem);
}

/* --- Ad band --------------------------------------------------------------
   A full-width row whose only job is to hold one ad unit, used by the pages
   that are a STACK OF SECTIONS rather than one article: the home page (below
   "How it works" and again above the footer) and the end of the blog index.
   Tool pages and single posts don't need it — their ad slots are children of
   `.tool-page` / `.content-page` and take their spacing from those.

   Vertical padding only, and only on the WRAPPER. That is the whole point of
   having a wrapper class at all: `.ad-slot` already centres itself
   (`margin-inline: auto`), and the mobile scale ladder above sizes it by
   selecting a BARE `.ad-slot` (0,1,0), so any width or height declared here as
   `.ad-band .ad-slot` (0,2,0) would out-specify the ladder and silently undo
   the mobile fit — the 728px banner would go back to being clipped at ~48% on a
   phone with no error anywhere. Nothing in this block touches the slot.

   The markup pairs this with an inner `.container`, whose
   `padding-inline: var(--s-4)` is the same 2x16px the ladder's steps are
   calculated against ((viewport - 36px) / 728, the 36px being that padding plus
   the slot's 2x2px border). Keep them together.

   No `min-height`: like every other slot on the site this one reserves no
   desktop space, because the Adsterra snippet is a parser-blocking script that
   builds its 728x90 iframe before first paint. A self-sizing unit that fills
   late is handled by `.ad-slot--responsive`'s `min-height` above instead. */
.ad-band { padding: var(--s-5) 0; }
/* Above the footer the bottom half is already there: `.site-footer` carries
   `margin-top: var(--s-7)`. Doubling up would leave a 72px gap. */
.ad-band--footer { padding-bottom: 0; }


/* An inline "browse" trigger styled like a link but semantically a button. */
.linklike {
    border: 0; background: none; padding: 0; font: inherit;
    color: var(--c-accent); cursor: pointer; text-decoration: underline;
}

/* File preview area (left canvas, after files are added) */
.tool-files {
    background: var(--c-surface-2); border: 1px solid var(--c-line);
    border-radius: var(--r-lg); padding: var(--s-4);
}
.tool-files__head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--s-3); margin-bottom: var(--s-3);
}
.tool-files__label { font-size: var(--fs-sm); font-weight: 700; color: var(--c-ink-soft); }

/* Selected-files preview grid (built by runtime.js). Small visual cards —
   image/PDF-page thumbnail on top, name + size below, remove on hover — laid
   out 4-up like the reference design. */
.file-list {
    list-style: none; padding: 0; margin: 0;
    display: grid; gap: var(--s-3);
    grid-template-columns: repeat(4, 1fr);
}
/* A single uploaded file should sit centered in the canvas, not pinned to the
   far-left column with an empty gap beside it. When the list holds exactly one
   card, collapse to a single centered column and cap the card's width. */
.file-list:has(.file-card:only-child) {
    grid-template-columns: minmax(0, 240px);
    justify-content: center;
}
.file-card {
    position: relative;
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-md);
    display: flex; flex-direction: column;
}
/* `flex: 1` matters: the grid stretches every card to the tallest one in the
   row, and without it the thumb kept its natural height, leaving the preview
   and the filename stranded at the top with dead space underneath. Growing the
   thumb lets `place-items: center` do its job — image in the middle, name
   pushed to the bottom edge. No border-bottom: the divider read as a stray
   underline across the middle of the card. */
.file-card__thumb {
    flex: 1; min-height: 150px; display: grid; place-items: center;
    background: #fff; color: var(--c-muted);
    overflow: hidden;
    border-radius: var(--r-md) var(--r-md) 0 0;
}
.file-card__img { width: 100%; height: auto; display: block; }
.file-card__thumb canvas { width: 100%; height: auto; display: block; }
.file-card__meta { padding: var(--s-2); display: grid; gap: 1px; min-width: 0; }
.file-card__name {
    font-size: var(--fs-xs); font-weight: 600;
    overflow: hidden; text-overflow: ellipsis; white-space: nowrap;
}
.file-card__size { font-size: var(--fs-xs); color: var(--c-muted); }
.file-card__remove {
    position: absolute; top: 6px; right: 6px;
    width: 24px; height: 24px; border-radius: var(--r-full);
    border: 0; background: rgba(26, 26, 46, .55); color: #fff;
    font-size: var(--fs-sm); line-height: 1; cursor: pointer;
    display: grid; place-items: center;
    opacity: 0; transition: opacity .12s;
}
.file-card:hover .file-card__remove,
.file-card__remove:focus-visible { opacity: 1; }
.file-card__remove:hover { background: var(--c-danger); }

/* Hover tooltip pill: size + page count, centered over the thumbnail. Purely
   decorative — the same text is mirrored into the card's title attribute for
   keyboard/native access. */
.file-card__tip {
    position: absolute; left: 50%; top: -12px; transform: translate(-50%, -4px);
    z-index: 4; pointer-events: none; white-space: nowrap;
    padding: 3px 9px; border-radius: var(--r-full);
    background: rgba(26, 26, 46, .88); color: #fff;
    font-size: var(--fs-xs); font-weight: 600; line-height: 1.3;
    box-shadow: 0 2px 8px rgba(15, 15, 35, .25);
    opacity: 0; transition: opacity .12s, transform .12s;
}
.file-card:hover .file-card__tip,
.file-card:focus-within .file-card__tip { opacity: 1; transform: translate(-50%, 0); }

/* "See more" card at the end of a long list. */
.file-card--more { border-style: dashed; }
.file-card__more-btn {
    flex: 1; min-height: 200px; border: 0; background: transparent;
    font: inherit; font-size: var(--fs-sm); font-weight: 600;
    color: var(--c-accent); cursor: pointer;
}
.file-card__more-btn:hover { background: var(--c-accent-soft); }

/* --- Selected-page marks (Remove Pages / Extract Pages) -------------------
   A big cross or check stamped over a page thumbnail, plus a matching tint on
   the "Page N" label, so the selection reads at a glance instead of relying on
   dimming alone. Injected by page-range.js `markPage()`; only those two tools
   ever add `.page-mark`, so every other tool's cards are untouched. */
.file-card__thumb:has(> .page-mark) { position: relative; }
.page-mark {
    position: absolute; inset: 0; z-index: 3;
    display: grid; place-items: center;
    pointer-events: none;
}
.page-mark__svg {
    width: 56%; max-width: 96px; height: auto;
    filter: drop-shadow(0 1px 3px rgba(15, 15, 35, .35));
    animation: page-mark-in .18s ease-out;
}
@keyframes page-mark-in {
    from { opacity: 0; transform: scale(.6); }
    to   { opacity: 1; transform: scale(1); }
}
@media (prefers-reduced-motion: reduce) {
    .page-mark__svg { animation: none; }
}
/* Cross = this page is going away. Check = this page is being kept. */
.file-card--mark-cross .page-mark { color: var(--c-danger); }
.file-card--mark-check .page-mark { color: var(--c-success); }
.file-card--mark-cross .file-card__name { color: var(--c-danger); }
.file-card--mark-check .file-card__name { color: var(--c-success); }
.file-card--marked { border-color: currentColor; }
.file-card--mark-cross { color: var(--c-danger); }
.file-card--mark-check { color: var(--c-success); }

/* Legacy horizontal rows (server-tool.js still renders these). */
.file-list__item {
    display: flex; align-items: center; gap: var(--s-3);
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-md); padding: var(--s-3);
    grid-column: 1 / -1;
}
.file-list__name { flex: 1; font-size: var(--fs-sm); font-weight: 600; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.file-list__size { font-size: var(--fs-xs); color: var(--c-muted); }
.file-list__remove {
    border: 0; background: transparent; color: var(--c-muted);
    cursor: pointer; font-size: var(--fs-lg); line-height: 1; padding: 0 var(--s-2);
}
.file-list__remove:hover { color: var(--c-danger); }

/* --- Settings sidebar ----------------------------------------------------- */
.tool-sidebar {
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-sm);
    position: sticky; top: calc(var(--header-h, 64px) + var(--s-3));
    /* Cap the box to the viewport so the title header and the action bar stay
       visible even when the option list is long — the body scrolls between
       them rather than pushing the run button off-screen. */
    max-height: calc(100vh - var(--header-h, 64px) - var(--s-5));
    display: flex; flex-direction: column; overflow: hidden;
}
/* Same [hidden]-vs-display trap as the dropzone: keep the sidebar truly gone
   until runtime.js reveals it. */
.tool-sidebar[hidden] { display: none; }
/* Pinned header — never scrolls. */
.tool-sidebar__title {
    flex: 0 0 auto; margin: 0; padding: var(--s-4) var(--s-4) var(--s-3);
    font-size: var(--fs-lg); font-weight: 700;
    border-bottom: 1px solid var(--c-line);
}
/* Scrollable middle — the only part that moves when options overflow. */
.tool-sidebar__body {
    flex: 1 1 auto; min-height: 0; overflow-y: auto;
    padding: var(--s-4);
}
.tool-sidebar__body:empty { display: none; }
/* Pinned footer — the primary action stays reachable at all times. */
.tool-sidebar__actions {
    flex: 0 0 auto;
    padding: var(--s-4); border-top: 1px solid var(--c-line);
    background: var(--c-surface-2); display: grid; gap: var(--s-2);
}

/* In the wide-editor layout the sidebar is only title + action bar; let it sit
   as a normal (non-sticky) full-width strip below the editor, with no viewport
   cap and the title inline rather than a pinned header. */
.tool-workspace--wide .tool-sidebar {
    position: static; max-height: none;
    flex-direction: row; align-items: center; justify-content: space-between;
    flex-wrap: wrap; gap: var(--s-3);
}
.tool-workspace--wide .tool-sidebar__title { border-bottom: 0; padding: var(--s-4); }
.tool-workspace--wide .tool-sidebar__actions {
    border-top: 0; background: transparent; padding: var(--s-4);
    grid-auto-flow: column; justify-content: flex-end;
}
.tool-workspace--wide .tool-sidebar__actions .btn--block { width: auto; }

/* Options + per-tool option controls (opt-*) */

/* `minmax(0, 1fr)` and NOT the implicit `auto` track. This is the one line that
   keeps a phone from having to pinch-zoom the visual editors.

   A grid's `auto` track is at least the min-content width of what sits in it,
   and it wins over the container's own width — the track overflows instead of
   the content shrinking. Edit PDF, Annotate, Redact and Fill & Sign all render a
   page element carrying an INLINE pixel width (612–900px, the PDF's own size),
   so this track grew to a full desktop page on every screen narrower than one.
   The whole document then scrolled sideways, and the header, the ad slot and the
   footer — sized to the viewport, as they should be — filled less than half the
   width once you zoomed out far enough to see the editor.

   Worse for Edit PDF specifically: `fitToWidth()` in edit-pdf.js opens the file
   at whatever zoom fits `host.clientWidth`. Measured inside a blown-out track it
   read ~612px on a 384px phone and chose 99%, so the tool zoomed a page to a
   width the screen never had. With the track pinned to the container it now
   measures the real width and opens at a zoom that fits.

   No effect on the tools whose options are ordinary form controls: the column
   was already the container's width for them, and items still stretch to fill
   it. All this changes is that content WIDER than the column now overflows the
   column instead of dragging the page out with it. */
.tool-options { display: grid; grid-template-columns: minmax(0, 1fr); gap: var(--s-4); }
.tool-options:empty { display: none; }
.opt-group { border: 1px solid var(--c-line); border-radius: var(--r-md); padding: var(--s-3) var(--s-4); margin: 0; }
.opt-group legend { font-size: var(--fs-sm); font-weight: 700; padding: 0 var(--s-2); }
.opt-radio, .opt-check { display: flex; align-items: center; gap: var(--s-2); font-size: var(--fs-sm); padding: var(--s-1) 0; cursor: pointer; }
.opt-field { display: grid; gap: var(--s-2); font-size: var(--fs-sm); font-weight: 600; }
.opt-field input[type="text"], .opt-field input[type="number"], .opt-field input[type="password"], .opt-field select, .opt-field input[type="range"] {
    width: 100%;
    padding: var(--s-2) var(--s-3); border: 1px solid var(--c-line);
    border-radius: var(--r-md); font-size: var(--fs-sm); font-family: inherit; outline: none; font-weight: 400;
}
.opt-field input:focus, .opt-field select:focus { border-color: var(--c-accent); }
/* Paired option inputs (e.g. crop X/Y, width/height) sit side by side. */
.opt-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); }
.opt-note, .tool-note { font-size: var(--fs-sm); color: var(--c-muted); margin: 0; }

/* --- Split PDF — advanced options ----------------------------------------
   Only split.js renders `.split-*`, so nothing here can reach another tool.
   Sized for the 340px sidebar: the from/to pair sits two-up and the number
   inputs drop their own borders in favour of one on the wrapping label. */
.split-opts { display: grid; gap: var(--s-3); }

.split-modes { display: grid; gap: 2px; }
.split-mode {
    display: flex; align-items: flex-start; gap: var(--s-2);
    padding: var(--s-2); border-radius: var(--r-md);
    cursor: pointer; transition: background .12s ease;
}
.split-mode:hover { background: var(--c-surface-2); }
.split-mode input { flex: none; margin: 3px 0 0; accent-color: var(--c-accent); cursor: pointer; }
.split-mode__txt { display: grid; gap: 1px; }
.split-mode__t { font-size: var(--fs-sm); font-weight: 600; color: var(--c-ink); }
.split-mode__d { font-size: var(--fs-xs); font-weight: 400; color: var(--c-muted); line-height: 1.35; }
/* Highlight the chosen mode so the active panel below has a clear owner. */
.split-mode:has(input:checked) { background: var(--c-surface-2); }
.split-mode:has(input:checked) .split-mode__t { color: var(--c-accent); }

.split-panel { display: grid; gap: var(--s-3); }
.split-panel[hidden] { display: none; }

.split-ranges { list-style: none; margin: 0; padding: 0; display: grid; gap: var(--s-2); }
.split-range {
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    background: var(--c-surface-2); padding: var(--s-2) var(--s-3);
}
.split-range__head {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--s-2); margin-bottom: var(--s-2);
}
.split-range__n {
    font-size: var(--fs-xs); font-weight: 700; color: var(--c-ink-soft);
    text-transform: uppercase; letter-spacing: .04em;
}
.split-range__del {
    border: 0; background: none; cursor: pointer; color: var(--c-muted);
    font-size: var(--fs-base); line-height: 1; padding: 2px 7px;
    border-radius: var(--r-sm);
}
.split-range__del:hover { color: var(--c-danger); background: #fff; }
.split-range__del[hidden] { display: none; }
.split-range__row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-2); }

.split-num {
    display: flex; align-items: center; gap: var(--s-2);
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-md); padding: 4px var(--s-2); min-width: 0;
    cursor: text;
}
.split-num:focus-within { border-color: var(--c-accent); }
.split-num span { flex: none; font-size: var(--fs-xs); color: var(--c-muted); }
.split-num input {
    width: 100%; min-width: 0; border: 0; outline: none; background: transparent;
    font: inherit; font-size: var(--fs-sm); font-weight: 600; color: var(--c-ink);
    text-align: right; padding: 2px 0;
}

.split-add {
    display: inline-flex; align-items: center; justify-content: center; gap: var(--s-2);
    width: 100%; padding: var(--s-2) var(--s-3);
    border: 1px dashed color-mix(in srgb, var(--c-accent) 45%, var(--c-line));
    border-radius: var(--r-md); background: #fff;
    font: inherit; font-size: var(--fs-sm); font-weight: 600;
    color: var(--c-accent); cursor: pointer;
    transition: background .12s ease, border-color .12s ease;
}
.split-add:hover:not(:disabled) {
    background: color-mix(in srgb, var(--c-accent) 8%, #fff);
    border-color: var(--c-accent);
}
.split-add:disabled { opacity: .5; cursor: not-allowed; }
.split-add__plus { font-size: var(--fs-base); line-height: 1; }

.split-summary { margin: 0; font-size: var(--fs-xs); color: var(--c-muted); line-height: 1.45; }
.split-summary b { color: var(--c-ink); font-weight: 700; }

/* Compress image — live size display */
.compress-sizes {
    display: flex; align-items: center; gap: var(--s-3);
    padding: var(--s-2) var(--s-3);
    background: var(--c-surface-2); border-radius: var(--r-md);
    font-size: var(--fs-sm); color: var(--c-ink-soft);
}
.compress-size-item { display: flex; align-items: center; gap: var(--s-1); }
.compress-size-item strong { color: var(--c-ink); font-weight: 600; }
.compress-size-sep { color: var(--c-muted); font-size: var(--fs-xs); }

/* Rotate tool — Right/Left step buttons + angle display */
.rotate-controls {
    display: flex; align-items: center; gap: var(--s-3);
    margin-top: var(--s-2);
}
.rotate-btn {
    display: inline-flex; align-items: center; gap: var(--s-2);
    padding: var(--s-2) var(--s-4); border-radius: var(--r-full);
    border: 1.5px solid var(--c-line); background: var(--c-surface);
    color: var(--c-ink); font-size: var(--fs-sm); font-weight: 600;
    cursor: pointer; transition: background .15s, border-color .15s, color .15s;
}
.rotate-btn:hover { background: var(--c-accent-soft); border-color: var(--c-accent); color: var(--c-accent); }
.rotate-display {
    flex: 1; text-align: center; font-size: var(--fs-sm);
    font-weight: 700; color: var(--c-ink-soft); min-width: 70px;
}

/* Progress + spinner
   Users reported the working/uploading state was easy to miss, so the panel is
   a bordered card with a large rotating ring rather than a line of text with a
   small spinner beside it. */
.tool-progress {
    text-align: center; padding: var(--s-7) var(--s-5);
    background: var(--c-surface-2); border: 1px solid var(--c-line);
    border-radius: var(--r-lg); margin: var(--s-5) 0;
}
.tool-progress [data-role="progress-text"] {
    margin: 0; font-weight: 600; color: var(--c-ink);
}
.spinner {
    width: 64px; height: 64px; margin: 0 auto var(--s-4);
    border: 5px solid var(--c-surface-3); border-top-color: var(--c-accent);
    border-radius: 50%; animation: spin .8s linear infinite;
}
@keyframes spin { to { transform: rotate(360deg); } }

/* Countdown ring for the automatic download: a circle that empties as the
   seconds tick away, with the number of seconds left in the middle. */
.dl-count {
    display: flex; flex-direction: column; align-items: center;
    gap: var(--s-2); margin: var(--s-4) 0 0;
}
.dl-count__dial { position: relative; width: 72px; height: 72px; }
.dl-count__dial svg { width: 100%; height: 100%; transform: rotate(-90deg); }
.dl-count__track { fill: none; stroke: var(--c-surface-3); stroke-width: 6; }
.dl-count__bar {
    fill: none; stroke: var(--c-accent); stroke-width: 6; stroke-linecap: round;
    transition: stroke-dashoffset .25s linear;
}
.dl-count__num {
    position: absolute; inset: 0; display: flex;
    align-items: center; justify-content: center;
    font-size: var(--fs-lg); font-weight: 700; color: var(--c-ink);
    font-variant-numeric: tabular-nums;
}
.dl-count__label {
    margin: 0; font-size: var(--fs-sm); color: var(--c-ink-soft);
}
/* Once the download has fired the dial stops being a countdown, so it reads as
   a plain message instead of an empty ring. */
.dl-count.is-done .dl-count__dial { display: none; }

/* Respect a reduced-motion preference: the state must still be obvious, so the
   ring stays visible and simply doesn't spin. */
@media (prefers-reduced-motion: reduce) {
    .spinner { animation-duration: 2.4s; }
    .dl-count__bar { transition: none; }
}

/* Result / downloads */
.tool-result {
    background: var(--c-accent-soft); border: 1px solid var(--c-accent);
    border-radius: var(--r-lg); padding: var(--s-6) var(--s-5); text-align: center;
}
.tool-result__check { color: var(--c-success); margin: 0 auto var(--s-2); }
.tool-result__msg { font-weight: 600; margin-bottom: var(--s-4); }
.tool-result__downloads { display: grid; gap: var(--s-2); justify-content: center; margin-bottom: var(--s-4); }
.download-link { text-decoration: none; }

/* Two-stage result: a short "processing" screen (reads like moving to a
   download page) that swaps to the ready/download screen. */
.tool-result__stage[hidden] { display: none; }
.tool-result__status {
    font-weight: 600; color: var(--c-ink-soft);
    margin-top: var(--s-3);
}
.tool-result__auto {
    font-size: var(--fs-sm); color: var(--c-ink-soft); margin: 0;
}

/* Error banner */
.tool-error {
    background: #fde8e8; color: #9b1c1c; border: 1px solid #f8b4b4;
    border-radius: var(--r-md); padding: var(--s-3) var(--s-4); margin: var(--s-3) 0; font-size: var(--fs-sm);
}

/* Organize: visual page grid ------------------------------------------------
   Four pages per row (8 was too many to read at a glance), with wide gutters so
   the "add a blank page" handles have somewhere to live. --gutter drives both
   the column gap and the handle offsets, so they stay centred at every
   breakpoint. The grid's own inline padding is what gives the leading and
   trailing handles room to sit outside the first/last card. */
.page-grid {
    --gutter: 2rem;
    display: grid;
    grid-template-columns: repeat(4, minmax(0, 1fr));
    row-gap: var(--s-5);
    column-gap: var(--gutter);
    padding-inline: var(--gutter);
}
@media (max-width: 900px) {
    .page-grid { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}
@media (max-width: 640px) {
    .page-grid { --gutter: 1.5rem; grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
.page-cell {
    position: relative;
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    background: #fff; cursor: grab;
}
.page-cell--dragging { opacity: .4; }
.page-cell--deleted { opacity: .4; outline: 2px solid var(--c-danger); }
/* The cell can no longer clip (the insert handles sit outside it), so the
   thumbnail rounds and clips its own top corners instead. */
.page-cell__thumb {
    position: relative;
    display: grid; place-items: center; padding: var(--s-2);
    background: var(--c-surface-2); overflow: hidden;
    border-radius: var(--r-md) var(--r-md) 0 0;
}
.page-cell__thumb canvas { max-width: 100%; height: auto; box-shadow: var(--sh-sm); }
.page-cell__bar { display: flex; align-items: center; gap: var(--s-1); padding: var(--s-1) var(--s-2); }
.page-cell__num { flex: 1; font-size: var(--fs-xs); color: var(--c-muted); }
.page-cell__btn { border: 0; background: transparent; cursor: pointer; font-size: var(--fs-base); line-height: 1; padding: 2px 4px; color: var(--c-ink-soft); }
.page-cell__btn:hover { color: var(--c-accent); }

/* An inserted blank page: dashed outline plus a badge, so it can't be mistaken
   for a page that failed to render. */
.page-cell--blank { border-style: dashed; }
.page-cell__tag {
    position: absolute; left: 50%; top: 50%;
    transform: translate(-50%, -50%);
    padding: 3px 10px; border-radius: var(--r-full);
    background: var(--c-accent-soft); color: var(--c-accent);
    font-size: var(--fs-xs); font-weight: 700; white-space: nowrap;
}

/* "Add a blank page" handles, one in each gutter beside a page. Hidden until
   the page is hovered or keyboard-focused so the grid stays calm. */
.page-insert {
    position: absolute; top: 50%; z-index: 5;
    width: 32px; height: 32px; margin-top: -16px;
    display: grid; place-items: center;
    border: 1px solid var(--c-line); border-radius: var(--r-full);
    background: #fff; color: var(--c-accent); cursor: pointer;
    box-shadow: var(--sh-sm);
    opacity: 0; transform: scale(.8); pointer-events: none;
    transition: opacity .14s ease, transform .14s ease;
}
.page-insert--before { left: calc(var(--gutter) / -2 - 16px); }
.page-insert--after { right: calc(var(--gutter) / -2 - 16px); }
.page-cell:hover > .page-insert,
.page-cell:focus-within > .page-insert {
    opacity: 1; transform: scale(1); pointer-events: auto;
}
.page-insert:hover { border-color: var(--c-accent); background: var(--c-accent-soft); }
/* Tooltip built from the button's own data-tip, matching the file-card pill. */
.page-insert::after {
    content: attr(data-tip);
    position: absolute; bottom: calc(100% + 8px); left: 50%;
    transform: translateX(-50%);
    padding: 5px 10px; border-radius: var(--r-sm);
    background: rgba(26, 26, 46, .92); color: #fff;
    font-size: var(--fs-xs); font-weight: 600; line-height: 1.3;
    white-space: nowrap; pointer-events: none;
    opacity: 0; transition: opacity .12s ease;
}
.page-insert:hover::after,
.page-insert:focus-visible::after { opacity: 1; }
@media (prefers-reduced-motion: reduce) {
    .page-insert { transition: none; }
}

/* Organize / Reorder Pages, Edit PDF, Fill & Sign, Annotate and Redact render
   every page of the document in their own editor below, so the generic "Your
   file" header and the single file card above it were pure duplication. Scoped
   by data-tool: every other workspace keeps them, and the <ul
   data-role="file-list"> stays in the DOM so runtime.js is unaffected. */
.tool-app[data-tool="organize-pdf"] .tool-files__head,
.tool-app[data-tool="organize-pdf"] .file-list,
.tool-app[data-tool="edit-pdf"] .tool-files__head,
.tool-app[data-tool="edit-pdf"] .file-list,
.tool-app[data-tool="annotate-pdf"] .tool-files__head,
.tool-app[data-tool="annotate-pdf"] .file-list,
.tool-app[data-tool="redact-pdf"] .tool-files__head,
.tool-app[data-tool="redact-pdf"] .file-list,
.tool-app[data-tool="fill-sign-pdf"] .tool-files__head,
.tool-app[data-tool="fill-sign-pdf"] .file-list { display: none; }

.is-hidden { display: none !important; }

/* --- Scan to PDF — camera + cleanup --------------------------------------
   Only scan-to-pdf.js renders `.scan-*`, so nothing here can reach another
   tool. Two pieces: the settings/preview controls that live in the 340px
   sidebar, and the fullscreen camera viewfinder. */

/* "Use camera" in the empty dropzone. The dropzone is a centred flex column,
   so this simply stacks under the "or drop files here" hint. */
.scan-cam-open-btn { margin-top: var(--s-3); }
.scan-icon { flex: none; }

/* Live cleanup preview. A fixed-height frame keeps the sidebar from jumping
   as portrait and landscape photos swap in. */
.scan-preview {
    display: grid; gap: var(--s-2);
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    padding: var(--s-3); background: var(--c-surface-2);
}
.scan-preview__label {
    font-size: var(--fs-xs); font-weight: 700; text-transform: uppercase;
    letter-spacing: .04em; color: var(--c-muted);
}
.scan-preview__frame {
    display: flex; align-items: center; justify-content: center;
    height: 180px; background: #fff;
    border: 1px solid var(--c-line); border-radius: var(--r-sm);
    overflow: hidden;
}
.scan-preview__canvas { max-width: 100%; max-height: 100%; display: block; }
.scan-preview__hint {
    font-size: var(--fs-xs); color: var(--c-muted); text-align: center;
    padding: 0 var(--s-3);
}

/* Scan style: three segments in one pill, sized to fit the narrow sidebar. */
.scan-modes {
    display: grid; grid-template-columns: repeat(3, 1fr);
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    overflow: hidden; background: #fff;
}
.scan-mode {
    border: 0; border-left: 1px solid var(--c-line); background: #fff;
    font: inherit; font-size: var(--fs-xs); font-weight: 600;
    color: var(--c-ink-soft); padding: var(--s-2) var(--s-1);
    cursor: pointer; text-align: center;
}
.scan-mode:first-child { border-left: 0; }
.scan-mode:hover { background: var(--c-surface-3); }
.scan-mode.is-on { background: var(--c-accent); color: #fff; border-left-color: var(--c-accent); }
.scan-mode.is-on + .scan-mode { border-left-color: var(--c-accent); }

/* Current slider value, shown next to its label. */
.scan-val {
    font-variant-numeric: tabular-nums; color: var(--c-accent);
    background: var(--c-accent-soft); border-radius: var(--r-sm);
    padding: 0 6px; font-size: var(--fs-xs);
}

/* .opt-check centres its content, which looks wrong when the label carries a
   second explanatory line — top-align these instead. */
.scan-check { align-items: flex-start; }
.scan-check input { margin-top: 3px; }
.scan-check small { display: block; font-weight: 400; color: var(--c-muted); font-size: var(--fs-xs); }

/* Viewfinder. Fixed to the whole window because a live video is unusable in a
   340px column, and dark so the page being shot is the only bright thing. */
.scan-cam {
    position: fixed; inset: 0; z-index: 200;
    display: grid; grid-template-rows: 1fr auto auto;
    background: #0c0c14;
}
body.scan-cam-open { overflow: hidden; }

.scan-cam__stage { position: relative; overflow: hidden; }
.scan-cam__video {
    width: 100%; height: 100%; object-fit: contain;
    display: block; background: #000;
}
/* Framing guide — a hint to fill the frame with the page, not a crop boundary. */
.scan-cam__guide {
    position: absolute; inset: 8% 6%;
    border: 2px dashed rgba(255,255,255,.45); border-radius: var(--r-md);
    pointer-events: none;
}
.scan-cam__msg {
    position: absolute; inset: auto var(--s-5) 50%;
    transform: translateY(50%);
    margin: 0 auto; max-width: 420px;
    background: rgba(255,255,255,.96); color: var(--c-ink);
    border-radius: var(--r-md); padding: var(--s-4);
    font-size: var(--fs-sm); line-height: 1.5; text-align: center;
}
.scan-cam__msg[hidden] { display: none; }

.scan-cam__bar {
    display: grid; grid-template-columns: 1fr auto 1fr;
    align-items: center; gap: var(--s-3);
    padding: var(--s-4) var(--s-5);
}
.scan-cam__btn {
    border: 1px solid rgba(255,255,255,.35); background: transparent;
    color: #fff; font: inherit; font-size: var(--fs-sm); font-weight: 600;
    border-radius: var(--r-full); padding: var(--s-2) var(--s-4);
    cursor: pointer; justify-self: start;
}
.scan-cam__btn:last-child { justify-self: end; }
.scan-cam__btn:hover { background: rgba(255,255,255,.14); }
.scan-cam__btn[hidden] { display: none; }

.scan-cam__shutter {
    width: 68px; height: 68px; border-radius: 50%;
    border: 3px solid rgba(255,255,255,.9); background: transparent;
    display: grid; place-items: center; cursor: pointer; padding: 0;
}
.scan-cam__shutter-ring {
    width: 52px; height: 52px; border-radius: 50%; background: #fff;
    transition: transform .1s;
}
.scan-cam__shutter:active .scan-cam__shutter-ring { transform: scale(.88); }
.scan-cam__shutter:disabled { opacity: .4; cursor: not-allowed; }
.scan-cam__shutter:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 3px; }

.scan-cam__count {
    margin: 0; padding: 0 var(--s-5) var(--s-5);
    text-align: center; color: rgba(255,255,255,.72); font-size: var(--fs-sm);
}

/* Shutter feedback: one white flash over the frame per capture. */
.scan-cam--flash .scan-cam__stage::after {
    content: ''; position: absolute; inset: 0; background: #fff;
    animation: scan-flash .18s ease-out;
}
@keyframes scan-flash { from { opacity: .85; } to { opacity: 0; } }

/* --- Visual PDF editors (Edit / Annotate / Redact / Fill & Sign) ----------- */
.pdfed-bar {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
    background: #fff; border: 1px solid var(--c-line); border-radius: var(--r-md);
    padding: var(--s-2) var(--s-3); margin-bottom: var(--s-3);
    position: sticky; top: calc(var(--header-h, 64px) + 8px); z-index: 5;
}
.pdfed-tool {
    border: 1px solid var(--c-line); background: #fff; border-radius: var(--r-full);
    padding: var(--s-1) var(--s-3); font: inherit; font-size: var(--fs-sm);
    font-weight: 600; cursor: pointer; color: var(--c-ink-soft);
}
.pdfed-tool:hover { background: var(--c-surface-3); }
.pdfed-tool.is-active { background: var(--c-accent); border-color: var(--c-accent); color: #fff; }
.pdfed-opt { display: inline-flex; align-items: center; gap: var(--s-1); font-size: var(--fs-xs); font-weight: 600; color: var(--c-muted); }
.pdfed-opt input { font: inherit; }

/* Divider between the tool buttons and the type controls. */
.pdfed-sep {
    width: 1px; align-self: stretch; margin: 0 var(--s-1);
    background: var(--c-line);
}

/* Bold / Italic. Square so the pair reads as a set, unlike the pill tools. */
.pdfed-toggle {
    border: 1px solid var(--c-line); background: #fff; border-radius: var(--r-sm);
    min-width: 30px; padding: var(--s-1) var(--s-2); font: inherit;
    font-size: var(--fs-sm); line-height: 1.1; cursor: pointer; color: var(--c-ink-soft);
}
.pdfed-toggle:hover { background: var(--c-surface-3); }
.pdfed-toggle.is-on { background: var(--c-accent); border-color: var(--c-accent); color: #fff; }

/* The family menu previews each face in itself, so it needs room to breathe. */
.pdfed-fontsel { max-width: 190px; font-size: var(--fs-sm); padding: 3px 6px; }

/* `minmax(0, 1fr)`, not the implicit `auto` track — the same reason as
   `.tool-options`, and the line that actually makes Annotate, Redact and
   Fill & Sign fit a phone.

   `.pdfed-page` carries an inline pixel width (up to 900 — whatever scale
   pdf.js rendered at) AND `max-width: 100%`, and that second declaration was
   doing nothing at all. An `auto` track is sized from its items' min-content
   contribution, and a PERCENTAGE max-width is ignored while a track is being
   measured, so the track came out at the page's full 900px. The percentage then
   resolved against that 900px track and returned 900px. The page stayed
   desktop-sized on a phone and dragged the whole document sideways with it,
   which is why the header, the ad slot and the footer — correctly sized to the
   viewport — ended up filling less than half the scrollable width.

   Pinned to the container, the 100% finally means the container: the page
   shrinks, `.pdfed-page canvas` (`max-width: 100%; height: auto`) shrinks with
   it, and `fitLayer()` in pdf-canvas-editor.js rescales the text layer by
   `wrap.clientWidth / baseWidth` through its ResizeObserver — so text boxes and
   placed signatures stay glued to the picture at any width. Pointer maths is
   ratio-based off `getBoundingClientRect()`, so it needs nothing. */
.pdfed-pages {
    display: grid; grid-template-columns: minmax(0, 1fr);
    gap: var(--s-4); justify-items: center;
}
/* Deliberately NOT `overflow: hidden`. A text box placed near an edge hangs its
   move and delete handles just outside itself, and clipping them made text at
   the right margin — a signature, typically — impossible to delete. The rounded
   corners move onto the canvas instead, which is what was being clipped for. */
.pdfed-page {
    position: relative; box-shadow: var(--sh-md); background: #fff; max-width: 100%;
}
.pdfed-page canvas { display: block; max-width: 100%; height: auto; border-radius: 4px; }
/* `none`, not `pinch-zoom`. The site now locks zoom on mobile at the viewport
   (`user-scalable=no`), so nothing anywhere is meant to pinch-zoom — the document
   area must match. `none` also ENFORCES that on iOS Safari, which ignores the
   viewport flag for accessibility but does honour touch-action. Single-finger
   drawing is unaffected: the drawing handlers already claim the swipe with
   preventDefault, so a one-finger drag draws exactly as before; only the browser's
   two-finger pinch is removed. (This reverses an earlier `pinch-zoom` here, which
   only existed while the rest of the site still allowed zoom.) */
.pdfed-overlay { position: absolute; inset: 0; cursor: crosshair; touch-action: none; }
.pdfed-pagenum {
    position: absolute; top: 6px; left: 8px; z-index: 2;
    background: rgba(26,26,46,.65); color: #fff; font-size: var(--fs-xs);
    padding: 2px 8px; border-radius: var(--r-full); pointer-events: none;
}

/* Typed text sits in a layer of real DOM boxes above the overlay canvas.
   The layer itself must NOT eat pointer events: it spans the whole page, so a
   highlight dragged anywhere would be swallowed before it reached the canvas.
   Only the boxes take clicks, and only while a text tool is chosen — hence the
   `.is-live` gate rather than a blanket `pointer-events: auto`.
   Width, height and `transform` are written inline by the module (the layer is
   scaled to match a page that `max-width: 100%` has shrunk), so nothing here may
   set those three properties. */
.pdfed-textlayer {
    position: absolute; top: 0; left: 0; z-index: 3;
    transform-origin: 0 0; pointer-events: none;
}
.pdfed-text { position: absolute; pointer-events: none; }
.pdfed-textlayer.is-live .pdfed-text { pointer-events: auto; }

.pdfed-text__in {
    min-width: 1.5em; min-height: 1em; padding: 0; margin: 0;
    outline: 1px dashed rgba(90, 90, 200, .55); outline-offset: 2px;
    white-space: pre-wrap; overflow-wrap: break-word;
    background: transparent; cursor: text;
}
.pdfed-text__in:focus { outline: 1px solid var(--c-accent); background: rgba(255,255,255,.55); }

/* Handles appear on hover or while typing — they are editing chrome, not part of
   the document, and they are never drawn into the saved PDF. */
.pdfed-text__grip,
.pdfed-text__del {
    position: absolute; top: -9px; width: 18px; height: 18px; padding: 0;
    border: 1px solid var(--c-line); border-radius: var(--r-full);
    background: #fff; color: var(--c-ink-soft); font: inherit; font-size: 12px;
    line-height: 16px; text-align: center; opacity: 0; transition: opacity .12s ease;
    pointer-events: none;
}
.pdfed-text:hover .pdfed-text__grip,
.pdfed-text:hover .pdfed-text__del,
.pdfed-text__in:focus ~ .pdfed-text__del,
.pdfed-text:focus-within .pdfed-text__grip,
.pdfed-text:focus-within .pdfed-text__del { opacity: 1; pointer-events: auto; }

/* The box the toolbar is currently editing. An OUTLINE, matching the dashed one
   above rather than adding a border: a border would take part in layout, and
   `box-sizing: border-box` with no set width means 3px of it shifts the glyphs
   off the position the PDF will be written at — the box would jump on selection
   and back again on deselect.
   Solid where the resting state is dashed: dashed says "you can click this",
   solid says "Font, Size, Color, B and I are pointed here". */
.pdfed-text.is-selected .pdfed-text__in {
    outline: 2px solid rgba(90, 90, 200, .85);
}
/* Selected AND holding the caret. The rule above outranks `.pdfed-text__in:focus`
   on specificity, so without this the accent focus ring disappeared the moment
   selection started following focus — leaving no way to tell the box you are
   typing in from a box the toolbar merely points at. */
.pdfed-text.is-selected .pdfed-text__in:focus {
    outline-color: var(--c-accent);
}

.pdfed-text__grip { left: -9px; cursor: move; }
.pdfed-text__grip::before { content: '\002B'; }
.pdfed-text__del { right: -9px; cursor: pointer; }
.pdfed-text__del:hover { background: #e02f2f; border-color: #e02f2f; color: #fff; }

/* A placed signature. It shares the text layer, but has its own `is-stamplive`
   gate: a signature stays draggable under the tool that placed it as well as
   under the text tools, because placing one and immediately wanting to nudge it
   onto the line is the normal case.
   Left, top, width and height are written inline by the module — do not set
   them here. */
.pdfed-stamp { position: absolute; pointer-events: none; }
.pdfed-textlayer.is-stamplive .pdfed-stamp {
    pointer-events: auto; cursor: move; touch-action: none;
}
.pdfed-stamp__img { display: block; width: 100%; height: 100%; user-select: none; }

/* The outline says "this is still an object, you can still move it". It is
   editing chrome and never reaches the PDF — only the <img> is saved. */
.pdfed-textlayer.is-stamplive .pdfed-stamp::after {
    content: ''; position: absolute; inset: -3px; border-radius: 3px;
    border: 1px dashed rgba(90, 90, 200, .55); pointer-events: none;
    opacity: 0; transition: opacity .12s ease;
}
.pdfed-textlayer.is-stamplive .pdfed-stamp:hover::after,
.pdfed-textlayer.is-stamplive .pdfed-stamp:focus-within::after { opacity: 1; }

.pdfed-stamp__size,
.pdfed-stamp__del {
    position: absolute; width: 18px; height: 18px; padding: 0;
    border: 1px solid var(--c-line); border-radius: var(--r-full);
    background: #fff; color: var(--c-ink-soft); font: inherit; font-size: 12px;
    line-height: 16px; text-align: center; opacity: 0; transition: opacity .12s ease;
    pointer-events: none;
}
/* `:focus-within` as well as `:hover`, and the box itself carries tabindex, so
   the handles are reachable without a hovering pointer. A text box gets this for
   free — its editable can be focused — but a stamp is a picture, and hover-only
   handles meant that on a phone a placed signature could be dragged and then
   never resized or deleted. */
.pdfed-textlayer.is-stamplive .pdfed-stamp:hover .pdfed-stamp__size,
.pdfed-textlayer.is-stamplive .pdfed-stamp:hover .pdfed-stamp__del,
.pdfed-textlayer.is-stamplive .pdfed-stamp:focus-within .pdfed-stamp__size,
.pdfed-textlayer.is-stamplive .pdfed-stamp:focus-within .pdfed-stamp__del {
    opacity: 1; pointer-events: auto;
}
.pdfed-stamp__del { top: -9px; right: -9px; cursor: pointer; }
.pdfed-stamp__del:hover { background: #e02f2f; border-color: #e02f2f; color: #fff; }
/* Bottom-right, where a resize corner is expected. `nwse-resize` rather than
   `se-resize` because the handle scales both ways along that diagonal. */
.pdfed-stamp__size { bottom: -9px; right: -9px; cursor: nwse-resize; }
.pdfed-stamp__size::before { content: '\25E2'; }

/* Fill & Sign signature pad */
.pdfed-signpad {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-3);
    background: #fff; border: 1px dashed var(--c-line); border-radius: var(--r-md);
    padding: var(--s-3); margin-bottom: var(--s-3);
}
.pdfed-signpad__label { font-size: var(--fs-sm); font-weight: 700; color: var(--c-ink-soft); }
.pdfed-signpad canvas {
    border: 1px solid var(--c-line); border-radius: var(--r-sm);
    background: #fcfcff; cursor: crosshair; touch-action: none; max-width: 100%;
}

/* Warning appended under the download link when a glyph was missing. */
.pdfed-charnote { color: #8a5a00; }

/* --- Shared type controls (`.pdff-*`) --------------------------------------
   Built by `assets/js/tools/pdf-fonts.js` and used by the tools that write text
   from a form rather than on a canvas: Page numbers, Watermark, Header & footer.
   A third prefix looks like one too many, but `.pdff-` is a distinct class token
   from both `.pdfe-` and `.pdfed-`, so these rules cannot leak into either
   editor — which is exactly why the row can be dropped into any tool safely. */

.pdff-row {
    display: flex; flex-wrap: wrap; align-items: flex-end; gap: var(--s-3);
}
.pdff-field { display: flex; flex-direction: column; gap: 4px; min-width: 0; }
.pdff-field > span {
    font-size: var(--fs-xs); font-weight: 700; color: var(--c-muted);
    text-transform: uppercase; letter-spacing: .04em;
}
.pdff-field select,
.pdff-field input { font: inherit; font-size: var(--fs-sm); }
.pdff-field select {
    padding: 6px 8px; border: 1px solid var(--c-line); border-radius: var(--r-sm);
    background: #fff; max-width: 220px;
}
/* Each option is set in its own face by the module, so the closed select shows a
   preview of the choice. Options inherit line-height from the OS menu, and a
   script face at the default size is unreadably small there. */
.pdff-field select option { font-size: 15px; padding: 2px 0; }
.pdff-field input[type="number"] { width: 84px; padding: 6px 8px; border: 1px solid var(--c-line); border-radius: var(--r-sm); }
.pdff-field input[type="color"] {
    width: 46px; height: 34px; padding: 2px; cursor: pointer;
    border: 1px solid var(--c-line); border-radius: var(--r-sm); background: #fff;
}
.pdff-field--style { justify-content: flex-end; }

.pdff-toggles { display: inline-flex; gap: 4px; }
.pdff-toggle {
    border: 1px solid var(--c-line); background: #fff; border-radius: var(--r-sm);
    width: 34px; height: 34px; font: inherit; font-size: var(--fs-sm);
    cursor: pointer; color: var(--c-ink-soft);
}
.pdff-toggle:hover { background: var(--c-surface-3); }
.pdff-toggle.is-on { background: var(--c-accent); border-color: var(--c-accent); color: #fff; }
.pdff-toggle:focus-visible { outline: 2px solid var(--c-accent); outline-offset: 2px; }

/* --- Edit PDF — the object editor ------------------------------------------
   ONLY edit-pdf.js renders `.pdfe-*`. These are deliberately separate from the
   `.pdfed-*` rules above, which belong to the shared canvas engine still used
   by Annotate, Redact and Fill & Sign. The two prefixes never match each other:
   a class selector matches whole tokens, so `.pdfe-page` cannot select an
   element classed `pdfed-page`. Do not merge the blocks.

   The size and position of a page, its canvas and every mark on it are written
   as INLINE styles by the module, because they depend on zoom and rotation.
   Nothing here may set width, height or max-width on those elements — a
   stylesheet `max-width: 100%` in particular silently defeats zooming. */

.pdfe-bar {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
    background: #fff; border: 1px solid var(--c-line); border-radius: var(--r-md);
    padding: var(--s-2) var(--s-3);
    position: sticky; top: calc(var(--header-h, 64px) + 8px); z-index: 6;
}
.pdfe-group {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-1);
}
/* A divider between groups rather than a gap, so the bar stays compact on the
   narrow screens where all three groups wrap onto separate rows. */
.pdfe-group + .pdfe-group {
    margin-left: var(--s-2); padding-left: var(--s-3);
    border-left: 1px solid var(--c-line);
}
.pdfe-tool {
    border: 1px solid var(--c-line); background: #fff; border-radius: var(--r-full);
    padding: var(--s-1) var(--s-3); font: inherit; font-size: var(--fs-sm);
    font-weight: 600; cursor: pointer; color: var(--c-ink-soft); white-space: nowrap;
}
.pdfe-tool:hover { background: var(--c-surface-3); }
.pdfe-tool.is-on { background: var(--c-accent); border-color: var(--c-accent); color: #fff; }
.pdfe-tool[disabled] { opacity: .45; cursor: default; }
.pdfe-tool[disabled]:hover { background: #fff; }

.pdfe-field {
    display: inline-flex; align-items: center; gap: var(--s-1);
    font-size: var(--fs-xs); font-weight: 600; color: var(--c-muted);
}
.pdfe-field input, .pdfe-field select {
    font: inherit; font-size: var(--fs-sm); color: var(--c-ink);
    border: 1px solid var(--c-line); border-radius: var(--r-sm);
    padding: 2px 4px; background: #fff;
}
.pdfe-field input[type="number"] { width: 4.5em; }
.pdfe-field input[type="color"] { width: 2.2em; height: 1.8em; padding: 1px; cursor: pointer; }

.pdfe-toggle {
    width: 2em; height: 2em; border: 1px solid var(--c-line); background: #fff;
    border-radius: var(--r-sm); font: inherit; font-size: var(--fs-sm);
    cursor: pointer; color: var(--c-ink-soft);
}
.pdfe-toggle:hover { background: var(--c-surface-3); }
.pdfe-toggle.is-on { background: var(--c-accent); border-color: var(--c-accent); color: #fff; }

.pdfe-pagebtn {
    width: 2em; height: 2em; display: inline-flex; align-items: center;
    justify-content: center; border: 1px solid var(--c-line); background: #fff;
    border-radius: var(--r-sm); font: inherit; font-size: var(--fs-sm);
    line-height: 1; cursor: pointer; color: var(--c-ink-soft);
}
.pdfe-pagebtn:hover { background: var(--c-surface-3); color: var(--c-ink); }
.pdfe-zoom {
    min-width: 4em; text-align: center; font-size: var(--fs-xs);
    font-weight: 700; color: var(--c-muted);
}

.pdfe-hint, .pdfe-status {
    font-size: var(--fs-sm); color: var(--c-muted); margin: var(--s-2) 0 0;
}
.pdfe-status--bad { color: var(--c-danger, #c0392b); font-weight: 600; }
.pdfe-flash {
    font-size: var(--fs-sm); font-weight: 600; color: var(--c-ink);
    background: #fff8d6; border: 1px solid #e9d98a; border-radius: var(--r-sm);
    padding: var(--s-2) var(--s-3); margin: var(--s-2) 0 0;
}
.pdfe-flash[hidden] { display: none; }

/* Bar for whatever is selected. Hidden via the `hidden` attribute, so it needs
   the explicit override — a class rule setting `display` beats [hidden]. */
.pdfe-objbar {
    display: flex; flex-wrap: wrap; align-items: center; gap: var(--s-2);
    background: var(--c-surface-2); border: 1px solid var(--c-line);
    border-radius: var(--r-md); padding: var(--s-2) var(--s-3); margin-top: var(--s-2);
}
.pdfe-objbar[hidden] { display: none !important; }
.pdfe-objbar__label { font-size: var(--fs-xs); font-weight: 700; color: var(--c-muted); }

/* --- Pages ---------------------------------------------------------------- */

/* Column gap leaves room for the per-page control strip, which is pulled up
   into it so the page element's own box stays exactly the page.

   `safe center` is not a typo and not decoration. Plain centring splits any
   overflow across BOTH sides, so a page zoomed wider than the card puts its
   left edge at a negative offset that no amount of scrolling can reach — the
   zoom control goes to 300%, so this is easy to hit. `safe` centres only while
   the page fits and falls back to start-aligned once it does not. Older
   browsers drop the second declaration and keep the first, which is the
   behaviour we had. */
/* Edit PDF is the one editor that must NOT shrink its page — the block comment
   above forbids `max-width` here, because zooming works by writing an inline
   pixel width. So a page zoomed past the width of the card has to be pannable
   INSIDE this box; left to overflow, it widens the document itself and strands
   the header, the ad slot and the footer at viewport width inside a much wider
   scroll area. `fitToWidth()` opens every file at a zoom that fits, so this only
   comes into play once the visitor zooms in deliberately — which is exactly when
   panning is what they want. It also finally makes the 300% ceiling usable on a
   desktop, where the same overflow escaped to the document.
   Note `overflow-x: auto` forces `overflow-y` to compute to `auto` as well; the
   height here is content-sized, so no vertical scrollbar appears.
   `padding-top`, not more margin: `.pdfe-pagebar` is lifted a full bar height
   (~34px) above its page by `translateY(-100%)`, the first page only has its own
   24px `margin-top` to be lifted into, and a scroll container clips whatever
   sits above its padding box — scrolling upwards past the origin is not a thing.
   Without this the first page's delete and rotate buttons lose their top edge. */
.pdfe-pages {
    display: flex; flex-direction: column; align-items: center;
    align-items: safe center;
    gap: var(--s-2); margin-top: var(--s-4);
    overflow-x: auto; overscroll-behavior-x: contain; padding-top: var(--s-3);
}
.pdfe-page {
    position: relative; flex: none; margin-top: var(--s-5);
    background: #fff; box-shadow: var(--sh-md); border-radius: 2px;
}
.pdfe-inner { position: absolute; overflow: hidden; }
.pdfe-canvas { position: absolute; top: 0; left: 0; display: block; }

/* Purely visual outlines over the document's own text. The object layer sits on
   top and hit-tests the runs itself, so these must never take a pointer event —
   edit-pdf.js depends on this line. */
.pdfe-hits { position: absolute; inset: 0; pointer-events: none; }
.pdfe-hit { position: absolute; border-radius: 2px; }
.pdfe-hits.is-live .pdfe-hit.is-hot {
    background: color-mix(in srgb, var(--c-accent) 12%, transparent);
    outline: 1px solid var(--c-accent);
}

/* `none` for the same reason as `.pdfed-overlay`: mobile zoom is locked site-wide,
   so the document must not pinch-zoom either. Single-finger drawing is unaffected
   (the handlers call preventDefault); only the browser's two-finger pinch is
   removed, and this is what enforces no-zoom on iOS, which ignores the viewport
   `user-scalable=no`. Edit PDF keeps its own +/- zoom buttons, so precise mobile
   placement doesn't depend on the browser pinch. */
.pdfe-layer { position: absolute; inset: 0; touch-action: none; }
.pdfe-layer[data-tool="text"] { cursor: text; }
.pdfe-layer[data-tool="select"] { cursor: default; }
.pdfe-layer[data-tool="image"] { cursor: copy; }
.pdfe-layer[data-tool="draw"],
.pdfe-layer[data-tool="rect"],
.pdfe-layer[data-tool="ellipse"],
.pdfe-layer[data-tool="line"],
.pdfe-layer[data-tool="whiteout"],
.pdfe-layer[data-tool="highlight"] { cursor: crosshair; }

/* Per-page controls, lifted into the gap above the page. */
.pdfe-pagebar {
    position: absolute; top: 0; right: 0; transform: translateY(-100%);
    display: flex; align-items: center; gap: 4px; padding-bottom: 4px;
}
.pdfe-pagenum {
    font-size: var(--fs-xs); font-weight: 700; color: var(--c-muted);
    margin-right: var(--s-1);
}
.pdfe-insert { display: flex; justify-content: center; padding: 2px 0; }
.pdfe-insertbtn {
    border: 1px dashed var(--c-line); background: transparent; color: var(--c-muted);
    border-radius: var(--r-full); padding: 2px var(--s-3); font: inherit;
    font-size: var(--fs-xs); font-weight: 600; cursor: pointer; opacity: .5;
}
.pdfe-insertbtn:hover { opacity: 1; background: #fff; color: var(--c-ink); }

/* --- Marks ---------------------------------------------------------------- */

.pdfe-obj { position: absolute; box-sizing: border-box; }
.pdfe-obj.is-selected { outline: 1px dashed var(--c-accent); outline-offset: 1px; }
.pdfe-obj--image img { display: block; width: 100%; height: 100%; }
.pdfe-obj--rect, .pdfe-obj--ellipse { border-style: solid; background: transparent; }
.pdfe-obj--ellipse { border-radius: 50%; }
.pdfe-obj--highlight { mix-blend-mode: multiply; opacity: .35; }
/* A freehand stroke's bounding box is mostly empty space. Leaving it clickable
   would block drawing anywhere near an earlier stroke, so it only becomes
   grabbable under the Move tool — which is exactly when you want to grab it. */
.pdfe-obj--draw, .pdfe-obj--line { pointer-events: none; }
.pdfe-layer[data-tool="select"] .pdfe-obj--draw,
.pdfe-layer[data-tool="select"] .pdfe-obj--line { pointer-events: auto; }
.pdfe-vec { position: absolute; top: 0; left: 0; overflow: visible; }

/* The editable region. Zero padding and border is not cosmetic: baselineDrop()
   in edit-pdf.js positions the first line assuming the text starts at the box's
   top edge, and any inset here moves the saved text away from where it was
   typed. `pre-wrap` keeps runs of spaces, which the PDF preserves too. */
.pdfe-textin {
    display: block; min-width: 1em; min-height: 1em; outline: none;
    padding: 0; border: 0; margin: 0;
    white-space: pre-wrap; overflow-wrap: break-word;
    /* The wrapper says "drag me" and the wrapper is what a drag grabs, but a
       click inside here places a caret instead — so the pointer has to stop
       promising a drag at the box's edge. */
    cursor: text;
}
.pdfe-obj--text { cursor: move; }
.pdfe-obj--text.is-selected { outline-color: var(--c-accent); }

/* Grip and delete button only appear on the selected mark, so a page full of
   edits is not covered in controls. Both are contenteditable="false" in the JS;
   without that they become part of the text. */
.pdfe-grip, .pdfe-del { display: none; position: absolute; z-index: 2; }
.pdfe-obj.is-selected > .pdfe-grip, .pdfe-obj.is-selected > .pdfe-del { display: block; }
.pdfe-grip {
    right: -5px; bottom: -5px; width: 10px; height: 10px;
    background: var(--c-accent); border: 1px solid #fff; border-radius: 2px;
    cursor: nwse-resize;
}
.pdfe-del {
    top: -10px; right: -10px; width: 18px; height: 18px; padding: 0;
    border: 1px solid #fff; border-radius: 50%; background: var(--c-accent);
    color: #fff; font: inherit; font-size: 12px; line-height: 1; cursor: pointer;
}

.pdfe-ghost {
    position: absolute; border: 1px dashed var(--c-accent); pointer-events: none;
}
.pdfe-ghost--highlight { opacity: .35; }
.pdfe-ghost--whiteout { background: rgba(255,255,255,.7); }

/* --- Category page head --------------------------------------------------- */
.section-head { text-align: center; padding: var(--s-6) var(--s-4) var(--s-4); }
.section-head h1 { font-size: clamp(1.6rem, 6vw, var(--fs-3xl)); overflow-wrap: break-word; }
.section-head p { color: var(--c-muted); }
.tool-card__badge { align-self: flex-start; }

/* --- Error pages ---------------------------------------------------------- */
.error-page { text-align: center; padding: var(--s-8) var(--s-4); max-width: 560px; margin: 0 auto; }
.error-code { font-size: 4rem; font-weight: 800; color: var(--c-accent); line-height: 1; margin: 0; }

/* --- Responsive ----------------------------------------------------------- */
@media (max-width: 1100px) {
    .tool-grid { grid-template-columns: repeat(4, 1fr); }
}
@media (max-width: 900px) {
    .tool-grid { grid-template-columns: repeat(3, 1fr); }
    /* Tool workspace: sidebar drops below the canvas as a normal block.
       `minmax(0, 1fr)` for the same reason as the two-column rule above — a bare
       `1fr` keeps an `auto` minimum, so an editor page would widen the track. */
    .tool-workspace { grid-template-columns: minmax(0, 1fr); }
    .tool-sidebar { position: static; }
    .file-list { grid-template-columns: repeat(3, 1fr); }
    .why-grid { grid-template-columns: repeat(2, 1fr); }
    .usecase-grid { grid-template-columns: repeat(2, 1fr); }
    .testimonial-grid { grid-template-columns: 1fr; }
    .trust-bar__grid { grid-template-columns: repeat(2, minmax(0, 1fr)); }
    .how-steps { grid-template-columns: 1fr; }
    /* Mobile header = two icon buttons: a hamburger (left) for nav options and
       a grid icon (right) for the profile/account. Hide the inline desktop nav
       and the avatar+name trigger label. */
    .site-nav { display: none; }
    .footer-top { grid-template-columns: 1fr 1fr; }
    .footer-brand { grid-column: 1 / -1; }

    /* Hamburger (nav options) sits on the far left, before the brand; the grid
       (profile) sits on the far right. Two clearly separated parts, like the
       reference. Flex order moves the hamburger ahead of the logo. */
    .navmenu { display: block; order: -1; }
    .navmenu .iconbtn { display: inline-flex; }
    .header-account { margin-left: auto; }  /* push the profile side to the right */

    /* Logged-in: show the user's real avatar (logo or Google photo) as the
       tap target — hide only the name and the generic fallback glyph, so a
       logo change in Settings shows up here too. */
    .usermenu__name { display: none; }
    .usermenu:not(.usermenu--guest) .usermenu__grid { display: none; }
    .usermenu__trigger { border: 0; background: transparent; width: 42px; height: 42px; justify-content: center; padding: 0; border-radius: 50%; }
    .usermenu__trigger .avatar { display: block; width: 32px; height: 32px; border-radius: 50%; }
    /* Guests still need the profile glyph (they have no avatar). */
    .usermenu--guest .usermenu__grid { display: inline-flex; align-items: center; justify-content: center; color: var(--c-ink-soft); }

    /* Guest: hide inline log in / sign up, show the grid icon menu. */
    .header-account > .header-account__link,
    .header-account > .btn { display: none; }
    .usermenu--guest { display: block; }
    .usermenu--guest .iconbtn { display: inline-flex; }
}
@media (max-width: 560px) {
    /* File preview cards: two per row on phones. */
    .file-list { grid-template-columns: repeat(2, 1fr); }
    /* One card per row. Each card lays out horizontally — icon top-left, title
       beside it, description spanning the full width below — so nothing wraps
       awkwardly or gets clipped. */
    .tool-grid { grid-template-columns: minmax(0, 1fr); }
    .tool-card {
        display: grid;
        grid-template-columns: auto minmax(0, 1fr);
        grid-template-areas:
            "icon name"
            "desc desc";
        column-gap: var(--s-3); row-gap: var(--s-2);
        align-items: center;
        padding: var(--s-4);
    }
    .tool-card__icon { grid-area: icon; margin-bottom: 0; width: 40px; height: 40px; }
    .tool-card__name { grid-area: name; font-size: var(--fs-base); padding-right: 64px; }
    .tool-card__desc { grid-area: desc; }

    .why-grid { grid-template-columns: 1fr; }
    .usecase-grid { grid-template-columns: 1fr; }
    .footer-bottom { flex-direction: column; }
    .footer-top { grid-template-columns: 1fr; }

    /* --- Phone: the visual editors --------------------------------------- */

    /* Every pixel of width here is a pixel of document. The editor sits inside
       `.tool-files` inside `.tool-page`, and at 16px of padding each, the two
       together take 64px — a sixth of a 384px phone — before the page is drawn.
       Scoped to `--wide` so only the five editors are affected; every other
       tool's file area keeps the padding it has. */
    .tool-workspace--wide > .tool-canvas > .tool-files { padding: var(--s-3); }

    /* Both bars wrap onto three or four rows at this width. `.pdfe-group`'s
       divider is a LEFT border, which reads correctly between two groups on one
       row and as a stray rule hanging off the start of the row once they wrap —
       which, on a phone, is always. Same for `.pdfed-sep`, whose `align-self:
       stretch` makes it a full-height line wherever the wrap happens to put it.
       The rows are already separated by the bar's own row gap. */
    .pdfe-group + .pdfe-group { margin-left: 0; padding-left: 0; border-left: 0; }
    .pdfed-sep { display: none; }

    /* A wrapped bar is tall — three or four rows of controls — and sticky, so it
       can cover most of a short screen and leave a slot of document underneath.
       Scrolling it away and back is one flick; hunting for the page through a
       letterbox is not. The bars stay put on tablets and desktops, where they
       are one row and the trade does not arise. */
    .pdfe-bar, .pdfed-bar { position: static; }

    .mega__panel {
        position: fixed; left: var(--s-3); right: var(--s-3);
        width: auto; top: calc(var(--header-h) + 4px);
    }
    .mega__grid { grid-template-columns: 1fr 1fr; }
}

/* --- Header account links ------------------------------------------------- */
.site-account { display: flex; align-items: center; gap: var(--s-3); margin-left: var(--s-3); }
.site-account a { font-size: var(--fs-sm); font-weight: 600; color: var(--c-ink-soft); }
.site-account .btn { padding: var(--s-1) var(--s-3); }

/* --- Auth pages (login / signup / forgot / reset) ------------------------- */
/* --- Full-page auth layout (Login / Create account) ----------------------- */
body.auth-page {
    min-height: 100vh; background: var(--c-surface-2);
    display: flex; align-items: center; justify-content: center;
}
.auth-page__wrap {
    width: 100%; max-width: 460px; padding: var(--s-5) var(--s-4);
    display: flex; flex-direction: column; align-items: center; gap: var(--s-4);
}
.auth-page__brand {
    font-size: var(--fs-xl); font-weight: 800; color: var(--c-ink);
    letter-spacing: -0.02em; text-decoration: none; align-self: flex-start;
}
.auth-page__brand:hover { color: var(--c-accent); }
.auth-page__main { width: 100%; }
.auth-page__footer { font-size: var(--fs-xs); color: var(--c-muted); text-align: center; }
.auth-page__footer a { color: var(--c-muted); }
.authp {
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-md);
    padding: var(--s-6) var(--s-5); display: grid; gap: var(--s-4);
}
.authp__title { font-size: var(--fs-xl); font-weight: 800; margin: 0; text-align: center; }
.authp__sub { color: var(--c-muted); font-size: var(--fs-sm); margin: 0; text-align: center; }
.authp__or {
    display: flex; align-items: center; gap: var(--s-3);
    color: var(--c-muted); font-size: var(--fs-sm);
}
.authp__or::before, .authp__or::after { content: ''; flex: 1; height: 1px; background: var(--c-line); }
.authp__links {
    text-align: center; font-size: var(--fs-sm); color: var(--c-muted);
    display: flex; flex-wrap: wrap; gap: var(--s-2); justify-content: center;
}
.authp__links a { color: var(--c-accent); }

/* --- Auth (old .auth class — forgot/reset/message pages) ------------------ */
.auth-card {
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-sm); padding: var(--s-6) var(--s-5);
}
.auth-card h1 { text-align: center; margin-bottom: var(--s-2); }
.auth-sub { text-align: center; color: var(--c-muted); font-size: var(--fs-sm); margin-bottom: var(--s-5); }
.auth-form { display: grid; gap: var(--s-4); }
.auth-form label { display: grid; gap: var(--s-1); font-size: var(--fs-sm); font-weight: 600; }
.auth-form input[type="text"], .auth-form input[type="email"], .auth-form input[type="password"] {
    padding: var(--s-3); border: 1px solid var(--c-line); border-radius: var(--r-md);
    font-size: var(--fs-base); font-family: inherit; outline: none; font-weight: 400;
}
.auth-form input:focus { border-color: var(--c-accent); }
.auth-form input:disabled { background: var(--c-surface-3); color: var(--c-muted); }
.auth-check { display: flex !important; flex-direction: row; flex-wrap: nowrap; align-items: center; justify-content: flex-start; gap: var(--s-2) !important; font-weight: 400 !important; }
.auth-check span { flex: 0 0 auto; order: 1; }
.auth-check input { width: auto; flex: 0 0 auto; order: 2; margin: 0; }
.auth-hint { font-weight: 400; color: var(--c-muted); font-size: var(--fs-xs); }
.auth-links { text-align: center; font-size: var(--fs-sm); margin-top: var(--s-4); display: flex; gap: var(--s-2); justify-content: center; flex-wrap: wrap; }
.auth-links span { color: var(--c-line); }
.auth-or { display: flex; align-items: center; gap: var(--s-3); color: var(--c-muted); font-size: var(--fs-sm); margin: var(--s-4) 0; }
.auth-or::before, .auth-or::after { content: ""; flex: 1; height: 1px; background: var(--c-line); }
.auth-card--center { text-align: center; }
.auth-status {
    width: 56px; height: 56px; margin: 0 auto var(--s-4);
    display: grid; place-items: center; border-radius: var(--r-full);
    font-size: var(--fs-2xl); font-weight: 800;
}
.auth-status.is-ok { background: #ecfdf3; color: var(--c-success); }
.auth-status.is-error { background: #fde8e8; color: var(--c-danger); }

/* --- Notice card (unsubscribe / resubscribe confirmation) ----------------- */
.doc-page--narrow { max-width: 560px; }
.notice-card {
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-sm);
    padding: var(--s-7) var(--s-5); text-align: center; margin: var(--s-6) auto;
}
.notice-card h1 { margin: 0 0 var(--s-3); font-size: var(--fs-xl); }
.notice-card p { color: var(--c-ink-soft); font-size: var(--fs-sm); line-height: 1.6; margin: 0 auto var(--s-3); max-width: 40ch; }
.notice-card__icon {
    width: 60px; height: 60px; margin: 0 auto var(--s-4);
    display: grid; place-items: center; border-radius: var(--r-full);
    font-size: 28px; font-weight: 800;
}
.notice-card__icon--ok { background: #ecfdf3; color: #05603a; }
.notice-card__icon--warn { background: #fef3c7; color: #92400e; }
.notice-card__sub { font-size: var(--fs-sm); }
.notice-card__home { margin-top: var(--s-5) !important; }
.notice-card__home a { color: var(--c-accent); font-weight: 600; }

.btn--block { width: 100%; justify-content: center; }
.btn--google { width: 100%; justify-content: center; background: #fff; border-color: var(--c-line); color: var(--c-ink); }
.btn--google:hover { background: var(--c-surface-3); text-decoration: none; }
.btn--danger { background: var(--c-danger); color: #fff; }
.btn--danger:hover { background: #b91c1c; text-decoration: none; }

.alert { border-radius: var(--r-md); padding: var(--s-3) var(--s-4); font-size: var(--fs-sm); margin-bottom: var(--s-4); }
.alert--ok { background: #ecfdf3; color: #05603a; border: 1px solid #a6f4c5; }
.alert--error { background: #fde8e8; color: #9b1c1c; border: 1px solid #f8b4b4; }

/* --- Account area (dashboard / history / settings) ------------------------ */
/* `minmax(0, 1fr)`, not `1fr`: the activity table is nowrap, and on a tablet
   (701–900px, where the nav column is still there) an `auto` minimum would let
   it widen this track and scroll the page instead of scrolling inside
   `.table-wrap`. No visual change at any width. */
.account {
    max-width: var(--container); margin: 0 auto; padding: var(--s-6) var(--s-4);
    display: grid; grid-template-columns: 200px minmax(0, 1fr); gap: var(--s-6); align-items: start;
}
.account-main { min-width: 0; }
.account-nav {
    display: grid; gap: var(--s-1); position: sticky; top: calc(var(--s-6) + 60px);
    background: #fff; border: 1px solid var(--c-line); border-radius: var(--r-lg);
    padding: var(--s-3); box-shadow: var(--sh-sm);
}
.account-nav a {
    padding: var(--s-2) var(--s-3); border-radius: var(--r-md);
    color: var(--c-ink-soft); font-weight: 600; font-size: var(--fs-sm);
}
.account-nav a:hover { background: var(--c-surface-3); text-decoration: none; }
.account-nav a.is-active { background: var(--c-accent-soft); color: var(--c-accent); }

/* Profile hero — gradient banner with the avatar overlapping its lower edge. */
.profile-hero {
    background: #fff; border: 1px solid var(--c-line); border-radius: var(--r-lg);
    overflow: hidden; box-shadow: var(--sh-sm); margin-bottom: var(--s-5);
}
.profile-hero__banner {
    height: 96px;
    background: linear-gradient(120deg, var(--c-accent), #8b3fe0 55%, #db2777);
}
.profile-hero__body {
    display: flex; align-items: flex-end; gap: var(--s-4);
    padding: 0 var(--s-5) var(--s-4); margin-top: -40px; flex-wrap: wrap;
}
.profile-hero__avatar { flex: none; }
.profile-hero__avatar .avatar {
    width: 96px; height: 96px; border: 4px solid #fff; border-radius: var(--r-full);
    box-shadow: var(--sh-sm);
}
.profile-hero__info { flex: 1 1 auto; padding-bottom: var(--s-1); }
.profile-hero__name { margin: 0; font-size: var(--fs-xl); }
.profile-hero__email {
    margin: var(--s-1) 0 0; color: var(--c-muted); font-size: var(--fs-sm);
    display: flex; align-items: center; gap: var(--s-2); flex-wrap: wrap;
    /* An email address is one unbreakable token: flex-wrap can only break
       BETWEEN items, so without these the address sets the row's min-content
       and scrolls the page on a phone. */
    min-width: 0; overflow-wrap: anywhere;
}
.profile-hero__plan {
    align-self: center; margin-left: auto;
    background: var(--c-accent-soft); color: var(--c-accent);
    font-weight: 700; font-size: var(--fs-xs); text-transform: uppercase;
    letter-spacing: .04em; padding: var(--s-2) var(--s-3); border-radius: var(--r-full);
}
.account-main h2 { margin-top: var(--s-6); }
.account-main h2:first-of-type { margin-top: var(--s-4); }
.account-note { color: var(--c-muted); font-size: var(--fs-sm); }
.account-danger-title { color: var(--c-danger); }
.account-form { display: grid; gap: var(--s-4); max-width: 420px; }

/* Account dashboard tiles */
.account-cards { display: grid; gap: var(--s-4); grid-template-columns: repeat(3, 1fr); margin-bottom: var(--s-5); }
.account-tile {
    background: #fff; border: 1px solid var(--c-line); border-radius: var(--r-lg);
    padding: var(--s-4); display: grid; gap: var(--s-1);
    transition: transform .12s, box-shadow .12s;
}
.account-tile:hover { transform: translateY(-2px); box-shadow: var(--sh-md); }
.account-tile__ic {
    width: 38px; height: 38px; border-radius: var(--r-md);
    display: grid; place-items: center; margin-bottom: var(--s-2);
}
.account-tile__label { font-size: var(--fs-xs); color: var(--c-muted); text-transform: uppercase; letter-spacing: .04em; }
/* `white-space: nowrap` is what ARMS the ellipsis — without it the text wraps
   instead of truncating, and a long email overflows the tile. `min-width: 0`
   lets the tile shrink below that text's width inside its grid track. */
.account-tile__value { font-size: var(--fs-lg); font-weight: 700; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; min-width: 0; }
.account-empty { color: var(--c-muted); font-size: var(--fs-sm); }

/* Recent-activity table (metadata only) */
/* The cells below are `nowrap`, so this box is what keeps a wide table from
   scrolling the whole page. `overscroll-behavior-x: contain` stops a swipe that
   reaches the end of the table from turning into a browser back-navigation. */
.table-wrap { overflow-x: auto; overscroll-behavior-x: contain; }
.account-table { width: 100%; border-collapse: collapse; font-size: var(--fs-sm); }
.account-table th, .account-table td { text-align: left; padding: var(--s-2) var(--s-3); border-bottom: 1px solid var(--c-line); white-space: nowrap; }
.account-table th { color: var(--c-muted); font-weight: 600; }

/* Badge variants used in account/admin (warn/ok) */
.badge--warn { background: var(--c-warning); color: #fff; }
.badge--ok { background: var(--c-success); color: #fff; }

/* --- AI tool UI (text in, text out) ---------------------------------------
   Centered at a comfortable reading width so the textarea and its action bar
   don't sprawl across the full page on desktop (which pushed the Summarize
   button far off to the right). Matches the utility card width for a
   consistent workspace size across tool types. */
.ai-tool { display: grid; gap: var(--s-4); margin-bottom: var(--s-6);
    max-width: 820px; margin-left: auto; margin-right: auto; width: 100%; }

/* Extra controls (translate's from/to, etc.) sit in a neat responsive row. */
.ai-tool__extra { max-width: 340px; }
/* When a tool has more than one extra (e.g. Translate: from + to), let them
   sit side by side on wider screens instead of stacking full-width. */
.ai-tool__extra + .ai-tool__extra { margin-top: 0; }
@media (min-width: 560px) {
    .ai-tool__extra { max-width: none; }
    .ai-tool:has(.ai-tool__extra + .ai-tool__extra) .ai-tool__extra {
        display: inline-grid; width: calc(50% - (var(--s-3) / 2));
        vertical-align: top;
    }
    .ai-tool:has(.ai-tool__extra + .ai-tool__extra) .ai-tool__extra:first-of-type {
        margin-right: var(--s-3);
    }
}
.ai-tool .opt-field {
    display: grid; gap: var(--s-2);
    font-size: var(--fs-sm); font-weight: 600; color: var(--c-ink);
}
.ai-tool select,
.ai-tool input[type="text"] {
    width: 100%; padding: var(--s-3);
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    font: inherit; background: #fff; outline: none;
    transition: border-color .15s, box-shadow .15s;
}

/* The main text box: roomier, softer, with a clear focus ring. */
.ai-tool textarea {
    width: 100%; padding: var(--s-4); min-height: 220px;
    border: 1px solid var(--c-line); border-radius: var(--r-lg);
    background: var(--c-surface-2, #fbfbfd);
    font: inherit; line-height: 1.65; resize: vertical; outline: none;
    transition: border-color .15s, box-shadow .15s, background .15s;
}
.ai-tool textarea::placeholder { color: var(--c-muted); }
.ai-tool textarea:focus,
.ai-tool select:focus,
.ai-tool input[type="text"]:focus {
    border-color: var(--c-accent);
    box-shadow: 0 0 0 3px color-mix(in srgb, var(--c-accent) 18%, transparent);
    background: #fff;
}

.ai-tool__bar {
    display: flex; align-items: center; justify-content: space-between;
    gap: var(--s-3); flex-wrap: wrap;
}
.ai-tool__hint { color: var(--c-muted); font-size: var(--fs-xs); flex: 1; min-width: 200px; }
.ai-tool__bar .btn { min-width: 140px; }

/* Result card: accent-tinted header bar, generous body, subtle lift. */
.ai-tool__output {
    border: 1px solid var(--c-line); border-radius: var(--r-lg);
    background: #fff; overflow: hidden; box-shadow: var(--sh-sm);
}
.ai-tool__output-head {
    display: flex; align-items: center; justify-content: space-between;
    padding: var(--s-3) var(--s-4);
    background: color-mix(in srgb, var(--c-accent) 8%, #fff);
    border-bottom: 1px solid var(--c-line);
    font-size: var(--fs-sm); font-weight: 700; color: var(--c-ink);
}
.ai-tool__result {
    padding: var(--s-4) var(--s-5); word-wrap: break-word; overflow-wrap: anywhere;
    line-height: 1.7; font-size: var(--fs-base); color: var(--c-ink);
}
/* Rendered Markdown inside the result panel (headings, lists, bold, rules). */
.ai-tool__result > *:first-child { margin-top: 0; }
.ai-tool__result > *:last-child { margin-bottom: 0; }
.ai-tool__result p { margin: 0 0 var(--s-3); }
.ai-tool__result h1,
.ai-tool__result h2,
.ai-tool__result h3,
.ai-tool__result h4,
.ai-tool__result h5,
.ai-tool__result h6 {
    margin: var(--s-5) 0 var(--s-2); line-height: 1.3; font-weight: 700; color: var(--c-ink);
}
.ai-tool__result h1 { font-size: var(--fs-xl); }
.ai-tool__result h2 { font-size: var(--fs-lg); }
.ai-tool__result h3,
.ai-tool__result h4,
.ai-tool__result h5,
.ai-tool__result h6 { font-size: var(--fs-base); }
.ai-tool__result ul,
.ai-tool__result ol { margin: 0 0 var(--s-3); padding-left: 1.4em; }
.ai-tool__result li { margin: 0 0 var(--s-1); }
.ai-tool__result strong { font-weight: 700; }
.ai-tool__result em { font-style: italic; }
.ai-tool__result code {
    font-family: ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
    font-size: 0.9em; background: color-mix(in srgb, var(--c-ink) 8%, transparent);
    padding: 0.1em 0.35em; border-radius: 4px;
}
.ai-tool__result a { color: var(--c-accent); text-decoration: underline; }
.ai-tool__result hr {
    border: 0; border-top: 1px solid var(--c-line);
    margin: var(--s-4) 0;
}

/* --- Utility widgets (Phase 5) -------------------------------------------
   Utilities build their own UI inside .util-tool. It's now a white card so the
   controls read as a tidy panel instead of loose elements on the page. */
.util-tool {
    /* width:100% is required: .tool-page is a flex column, and margin:0 auto on
       a flex item cancels the default cross-axis stretch — without an explicit
       width the card collapses to its content (~315px) instead of filling up to
       the 820px cap. */
    max-width: 820px; width: 100%; margin: 0 auto;
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-sm);
    padding: var(--s-5);
}
/* Safety net: no control may exceed the card. File inputs in particular have an
   intrinsic width (the "Choose File / No file chosen" text) that ignores
   width:100%, so cap it here to stop right-side overflow on phones. */
.util-tool input, .util-tool select, .util-tool textarea { max-width: 100%; }
.util-tool input[type="file"] { width: 100%; }
/* Keep the label-over-control grid (with a real gap) inside utility tools.
   This was display:block before, which glued every label to its input across
   ALL utility tools (QR, Loan/EMI, Age/Date, Password, …). */
.util-tool .opt-field {
    display: grid; gap: var(--s-2);
    margin-bottom: var(--s-4); font-size: var(--fs-sm); font-weight: 600;
}
/* Rows several util modules render but that had no CSS (hence the mess):
   a two-up field row and a compact inline input. */
.util-row { display: grid; grid-template-columns: 1fr 1fr; gap: var(--s-3); margin-bottom: var(--s-3); }
.util-input-sm {
    width: 100%; padding: var(--s-2) var(--s-3);
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    font-size: var(--fs-sm); font-family: inherit; outline: none;
    background: #fff; color: var(--c-ink);
}
.util-input-sm:focus { border-color: var(--c-accent); }

/* Stat cards (Word Counter and friends): a tidy grid of number-over-label
   tiles spanning the full card width. These classes were rendered by the
   modules but never styled — which is why the stats collapsed into a corner. */
.util-stats {
    display: grid; grid-template-columns: repeat(3, 1fr);
    gap: var(--s-3); margin-bottom: var(--s-4);
}
.util-stat {
    background: var(--c-surface-2); border: 1px solid var(--c-line);
    border-radius: var(--r-md); padding: var(--s-3);
    display: grid; gap: 2px; text-align: center; min-width: 0;
}
.util-stat__num {
    font-size: var(--fs-xl); font-weight: 800; color: var(--c-accent);
    line-height: 1.15; font-variant-numeric: tabular-nums;
    min-width: 0; overflow-wrap: anywhere;
}
.util-stat__label { font-size: var(--fs-xs); color: var(--c-muted); font-weight: 600; }

/* Stopwatch / Timer: centered clock layout. The display is the hero — a big
   tabular-numerals readout on a soft panel — with mode tabs above and the
   Start/Reset actions below, all centered so the card has no dead space. */
.util-tabs {
    display: flex; justify-content: center; gap: var(--s-2);
    margin-bottom: var(--s-4);
}
.util-timer__display {
    font-size: clamp(3rem, 9vw, 4.5rem); font-weight: 800;
    font-variant-numeric: tabular-nums; letter-spacing: 0.02em;
    color: var(--c-ink); text-align: center; line-height: 1.1;
    background: var(--c-surface-2); border: 1px solid var(--c-line);
    border-radius: var(--r-lg); padding: var(--s-6) var(--s-4);
    margin-bottom: var(--s-4);
}
.util-actions {
    display: flex; justify-content: center; gap: var(--s-3);
}
.util-actions .btn { min-width: 120px; justify-content: center; }
/* Countdown minutes/seconds inputs sit centered above the clock. */
.util-tool .util-row { justify-content: center; max-width: 320px; margin: 0 auto var(--s-3); }

/* Unit Converter: Category row with air below it, then FROM ⇅ TO columns with
   real spacing, then the live result banner. */
.uc-category { margin-bottom: var(--s-5) !important; }
/* Gap between the "Category" label text and its dropdown (the .opt-field grid
   gap is overridden by .util-tool's display:block, so restore it here). */
.uc-category select { margin-top: var(--s-2); }
.uc-grid {
    display: grid; grid-template-columns: 1fr auto 1fr;
    gap: var(--s-4); align-items: center; margin-bottom: var(--s-4);
}
.uc-side { display: grid; gap: var(--s-3); min-width: 0; }
.uc-swap {
    width: 44px; height: 44px; border-radius: var(--r-full);
    border: 1px solid var(--c-line); background: var(--c-accent-soft);
    color: var(--c-accent); font-size: var(--fs-lg); font-weight: 700;
    cursor: pointer; align-self: center; justify-self: center;
    transition: background .15s, color .15s, transform .1s;
}
.uc-swap:hover { background: var(--c-accent); color: #fff; transform: scale(1.06); }

@media (max-width: 560px) {
    .uc-grid { grid-template-columns: 1fr; }
    .uc-swap { transform: rotate(0); margin: calc(var(--s-2) * -1) auto; }
}
.util-input {
    width: 100%; padding: var(--s-3);
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    font-size: var(--fs-base); font-family: inherit; outline: none;
    background: #fff; color: var(--c-ink);
}
.util-input:focus { border-color: var(--c-accent); }
textarea.util-input { min-height: 120px; resize: vertical; }
.util-mono { font-family: var(--font-mono); font-size: var(--fs-sm); }
.util-select {
    width: 100%; padding: var(--s-3);
    border: 1px solid var(--c-line); border-radius: var(--r-md);
    font-size: var(--fs-base); background: #fff; outline: none;
}
.util-select:focus { border-color: var(--c-accent); }
.util-output {
    padding: var(--s-4); background: var(--c-surface-3);
    border-radius: var(--r-md); text-align: center;
    font-size: var(--fs-lg); font-weight: 700; color: var(--c-ink);
}
.util-status { font-size: var(--fs-sm); font-weight: 600; margin: var(--s-2) 0; min-height: 1.2em; }
.util-status.is-ok { color: var(--c-success); }
.util-status.is-error { color: var(--c-danger); }
.util-btn-row { display: flex; flex-wrap: wrap; gap: var(--s-2); margin: var(--s-3) 0; }
.util-grid { display: grid; gap: var(--s-3); grid-template-columns: 1fr 1fr; }
.util-check { display: flex; align-items: center; gap: var(--s-2); font-weight: 500; }
/* Checkbox group (Password Generator): two tidy columns with real row gaps,
   and clear space before whatever follows (the Generate button). */
.util-checks {
    display: grid; grid-template-columns: repeat(2, minmax(0, 1fr));
    gap: var(--s-2) var(--s-4); margin: var(--s-4) 0 var(--s-5);
}
/* Output + Copy pairs: the value takes the width, the button stays compact. */
.util-copyrow {
    display: grid; grid-template-columns: minmax(0, 1fr) auto;
    gap: var(--s-3); align-items: center; margin-bottom: var(--s-4);
}
/* Buttons rendered directly into a utility card never touch the control
   above them. */
.util-tool > div > .btn, .util-tool [data-role="mount"] > div > .btn { margin-top: var(--s-2); }
.util-check input { width: auto; }

/* QR / barcode output */
.util-qr__out {
    display: flex; justify-content: center; padding: var(--s-5);
    background: #fff; border: 1px solid var(--c-line); border-radius: var(--r-md);
    margin: var(--s-4) 0; min-height: 80px; overflow: hidden;
}
/* qrcodejs renders a canvas at the literal pixel size (up to 512px). Constrain
   it so it never overflows a narrow phone screen. */
.util-qr__out canvas, .util-qr__out img { max-width: 100%; height: auto; }

/* Hash results */
.util-hash-results { display: grid; gap: var(--s-2); margin: var(--s-4) 0; }
.util-hash-row {
    display: grid; grid-template-columns: 70px 1fr auto; gap: var(--s-2);
    align-items: center; padding: var(--s-2);
    background: var(--c-surface-3); border-radius: var(--r-sm);
}
.util-hash-name { font-weight: 700; font-size: var(--fs-xs); text-transform: uppercase; color: var(--c-muted); }
.util-hash-val { font-family: var(--font-mono); font-size: var(--fs-xs); word-break: break-all; }

/* Color picker */
.cp-canvas { display: block; width: 100%; max-width: 100%; height: auto; border-radius: var(--r-md); border: 1px solid var(--c-line); cursor: crosshair; }
.cp-native { width: 60px; height: 40px; border: 1px solid var(--c-line); border-radius: var(--r-sm); background: none; cursor: pointer; }
.cp-readout { display: flex; align-items: center; gap: var(--s-4); margin: var(--s-4) 0; }
.cp-swatch { width: 64px; height: 64px; border-radius: var(--r-md); border: 1px solid var(--c-line); flex: none; }
.cp-out { font-family: var(--font-mono); font-size: var(--fs-sm); }
.cp-fields { display: grid; gap: var(--s-2); }
.cp-row { display: flex; align-items: center; gap: var(--s-2); }

@media (max-width: 560px) {
    .util-grid { grid-template-columns: 1fr; }
    .util-row { grid-template-columns: 1fr; }
    /* Stat tiles: stack in a SINGLE column on phones. Two-up squeezed the long
       figures (e.g. EMI's "12,165,836.57") wider than their tile, so the boxes
       spilled outside the card. One column gives each tile the full card width
       and keeps everything inside the box. */
    .util-stats { grid-template-columns: 1fr !important; }
    .util-stat__num { font-size: var(--fs-lg); }
    .util-hash-row { grid-template-columns: 60px 1fr; }
    /* Color picker: stack swatch + fields vertically on narrow screens. */
    .cp-readout { flex-wrap: wrap; }
    .cp-swatch { width: 48px; height: 48px; }
}

/* --- Content pages: blog + how-to (Phase 6) ------------------------------ */
.content-page { max-width: 760px; margin: 0 auto; padding: var(--s-5) var(--s-4); }
.content-head { margin-bottom: var(--s-5); }
.content-head h1 { margin-bottom: var(--s-2); font-size: clamp(1.6rem, 6vw, var(--fs-3xl)); overflow-wrap: break-word; }
.content-meta { color: var(--c-muted); font-size: var(--fs-sm); }
.content-hero {
    width: 100%; border-radius: var(--r-lg);
    margin: var(--s-4) 0; border: 1px solid var(--c-line);
}
/* Ad slots on a post. Only the vertical margin is set here: .ad-slot already
   centres itself horizontally, and the mobile scale ladder further up sizes it
   by selecting a BARE `.ad-slot` — declaring width or height here would be
   higher specificity (0,2,0) than that ladder (0,1,0) and would silently undo
   the whole mobile fit, putting the clipped-728px-banner bug back.
   No `order` rules needed either: .content-page is a plain block, unlike the
   flex column .tool-page, so the slots sit where the template writes them. */
.content-page > .ad-slot { margin-top: var(--s-5); margin-bottom: var(--s-5); }
.content-page > .ad-slot--footer { margin-bottom: 0; }
/* Body prose is 18px, not the --fs-lg (20px) it used to be. 20px is a
   marketing-page size: in a 760px reading column it gives ~55 characters a
   line, which is short enough that the eye jumps back to the left margin
   constantly and a long post feels longer than it is. 18px lands nearer the
   65-75 characters that reads comfortably.

   A literal rather than a token because the scale has no 18px step, and adding
   one to tokens.css would change type on every page of the site to fix the
   blog. --lh-base (1.55) is a ratio, so the leading shrinks with it. */
.content-body { line-height: var(--lh-base); color: var(--c-ink); font-size: 1.125rem; overflow-wrap: anywhere; }
.content-body h2 { margin-top: var(--s-6); }
.content-body h3 { margin-top: var(--s-5); }
/* Headings carry ids so a post can link to its own sections. Without this, an
   in-page jump lands with the heading pinned under the fixed site header. */
.content-body h2, .content-body h3, .content-body h4 {
    scroll-margin-top: calc(var(--header-h) + var(--s-4));
}
.content-body h4 { margin-top: var(--s-5); font-size: var(--fs-base); font-weight: 700; }
/* A body image is a block of its own, never inline with the prose around it. */
.content-body .md-img {
    display: block; width: 100%; height: auto;
    margin: var(--s-5) 0; border-radius: var(--r-lg);
    border: 1px solid var(--c-line);
}
/* Tables scroll inside their own box rather than widening the article. */
.md-table-wrap { overflow-x: auto; overscroll-behavior-x: contain; margin: 0 0 var(--s-4); }
.md-table { width: 100%; border-collapse: collapse; font-size: var(--fs-base); }
.md-table th, .md-table td {
    padding: var(--s-3); text-align: left;
    border: 1px solid var(--c-line); vertical-align: top;
}
.md-table th { background: var(--c-surface-3); font-weight: 700; }
.content-body del { color: var(--c-muted); }
.content-body p, .content-body ul, .content-body ol { margin-bottom: var(--s-4); }
.content-body ul, .content-body ol { padding-left: var(--s-5); }
.content-body li { margin-bottom: var(--s-2); }
.content-body pre {
    background: var(--c-ink); color: #e6e6ef; padding: var(--s-4);
    border-radius: var(--r-md); overflow-x: auto; font-family: var(--font-mono);
    font-size: var(--fs-sm); margin-bottom: var(--s-4);
}
.content-body code {
    font-family: var(--font-mono); font-size: 0.9em;
    background: var(--c-surface-3); padding: 1px 5px; border-radius: var(--r-sm);
}
.content-body pre code { background: none; padding: 0; }
.content-body blockquote {
    border-left: 3px solid var(--c-accent); margin: 0 0 var(--s-4);
    padding-left: var(--s-4); color: var(--c-ink-soft);
}
.content-body a { text-decoration: underline; }

/* --- Blog index: image-led post cards -------------------------------------
   Wider than the 760px reading column of a single post, because this is a
   browsing page: at >=980px it becomes two columns, so the grid needs room for
   two comfortable cards. minmax(0, 1fr) rather than 1fr — a long unbroken word
   in a title would otherwise set the track's minimum width and push the card
   past its column. */
.post-list {
    display: grid; gap: var(--s-5);
    grid-template-columns: minmax(0, 1fr);
    max-width: 1080px; margin: 0 auto; padding-inline: var(--s-4);
}
.post-card {
    display: flex; flex-direction: column;      /* image, then title, then text */
    background: #fff; border: 1px solid var(--c-line);
    border-radius: var(--r-lg); box-shadow: var(--sh-sm);
    overflow: hidden;                            /* clips the image to the radius */
    transition: transform .18s ease, box-shadow .18s ease, border-color .18s ease;
}
.post-card:hover { transform: translateY(-2px); box-shadow: var(--sh-md); border-color: #d8d8ea; }

/* Fixed 16:9 frame so a mix of tall and wide uploads still lines up, and so the
   card reserves its space before the image arrives — no layout shift on load. */
.post-card__media {
    display: block; position: relative;
    aspect-ratio: 16 / 9; background: var(--c-surface-3);
    overflow: hidden;
}
.post-card__media img {
    width: 100%; height: 100%; object-fit: cover; display: block;
    transition: transform .35s ease;
}
.post-card:hover .post-card__media img { transform: scale(1.03); }

/* No featured image: a branded tile, not an empty box. */
.post-card__media--empty {
    display: grid; place-items: center;
    background: linear-gradient(135deg, var(--c-accent-soft), #f4f2ff);
}
.post-card__media--empty svg {
    width: 44px; height: 44px; color: var(--c-accent); opacity: .55;
}

/* flex:1 lets every card in a row match height while "Read more" still sits on
   the bottom edge (margin-top:auto below), so a short excerpt doesn't leave the
   link floating in the middle of a tall card. */
.post-card__body {
    flex: 1; display: flex; flex-direction: column;
    padding: var(--s-5); min-width: 0;
}
.post-card__title {
    margin-bottom: var(--s-2);
    font-size: clamp(1.2rem, 3.4vw, var(--fs-xl));
    line-height: var(--lh-tight); overflow-wrap: break-word;
}
.post-card__title a { color: var(--c-ink); text-decoration: none; }
.post-card__title a:hover { color: var(--c-accent); }
.post-card__meta { color: var(--c-muted); font-size: var(--fs-sm); margin-bottom: var(--s-3); }
.post-card__excerpt {
    color: var(--c-ink-soft); font-size: var(--fs-base);
    line-height: var(--lh-base); margin-bottom: var(--s-4);
}
.post-card__more { margin-top: auto; align-self: start; font-weight: 700; color: var(--c-accent); }
.post-card__more:hover { text-decoration: underline; }

/* The blog index used .admin-empty, which is only defined in admin.css and so
   rendered unstyled to visitors. This is the public equivalent. */
.post-empty {
    max-width: 1080px; margin: 0 auto; padding: var(--s-6) var(--s-4);
    text-align: center; color: var(--c-muted);
}

@media (min-width: 980px) {
    .post-list { grid-template-columns: repeat(2, minmax(0, 1fr)); }
}
@media (max-width: 560px) {
    .post-list { gap: var(--s-4); }
    .post-card__body { padding: var(--s-4); }
}

/* Related-tools module on tool pages */
.related-tools { margin-top: var(--s-7); }
.related-tools h2 { margin-bottom: var(--s-4); }

/* --- Reduced motion ------------------------------------------------------- */
@media (prefers-reduced-motion: reduce) {
    html { scroll-behavior: auto; }
    * { transition: none !important; }
}

@media (max-width: 700px) {
    .account { grid-template-columns: 1fr; gap: var(--s-4); padding: var(--s-4) var(--s-3); }
    .account-main { min-width: 0; }
    .account-nav { display: none; }
    .account-cards { grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--s-3); }
    .profile-hero__body { justify-content: center; text-align: center; }
    .profile-hero__info { flex-basis: 100%; }
    .profile-hero__email { justify-content: center; }
    .profile-hero__plan { margin-left: 0; }
    .verify-banner { flex-direction: column; gap: var(--s-3); }
    .verify-banner__action { width: 100%; }
    .verify-banner__action .btn { width: 100%; }
    .avatar-choices { gap: var(--s-2); }
    .banner-choices { gap: var(--s-2); }
    .banner-swatch { width: 72px; }
    .auth-form, .admin-form { max-width: 100%; }
}

@media (max-width: 400px) {
    .account-cards { grid-template-columns: minmax(0, 1fr); }
}

/* --- Account: verify-email banner, plan card, Pro/VIP extras -------------- */

/* Prompt shown to unverified users on their profile (verify => free Pro). */
.verify-banner {
    display: flex; align-items: flex-start; gap: var(--s-4);
    background: linear-gradient(120deg, color-mix(in srgb, var(--c-accent) 10%, #fff), #fff);
    border: 1px solid color-mix(in srgb, var(--c-accent) 30%, var(--c-line));
    border-radius: var(--r-lg); padding: var(--s-4) var(--s-5);
    margin-bottom: var(--s-5); box-shadow: var(--sh-sm); flex-wrap: wrap;
}
.verify-banner__ic {
    flex: none; width: 40px; height: 40px; border-radius: var(--r-md);
    display: grid; place-items: center;
    background: var(--c-accent-soft); color: var(--c-accent);
}
.verify-banner__body { flex: 1 1 260px; min-width: 0; overflow-wrap: anywhere; }
.verify-banner__body strong { display: block; font-size: var(--fs-base); margin-bottom: 2px; }
.verify-banner__body p { margin: 0; color: var(--c-muted); font-size: var(--fs-sm); }
.verify-banner__action { flex: none; align-self: center; }

/* Plan summary card on the Plan & usage page. */
.plan-card {
    background: #fff; border: 1px solid var(--c-line); border-radius: var(--r-lg);
    padding: var(--s-5); box-shadow: var(--sh-sm); margin-bottom: var(--s-5);
}
.plan-card--pro {
    border-color: transparent;
    background:
        linear-gradient(#fff, #fff) padding-box,
        linear-gradient(120deg, #f59e0b, #db2777) border-box;
    border: 1.5px solid transparent;
}
.plan-card__head { display: flex; align-items: center; gap: var(--s-3); margin-bottom: var(--s-3); }
.plan-card__badge {
    background: var(--c-accent-soft); color: var(--c-accent);
    font-weight: 800; font-size: var(--fs-xs); text-transform: uppercase;
    letter-spacing: .05em; padding: var(--s-2) var(--s-3); border-radius: var(--r-full);
}
.plan-card__badge--pro { background: linear-gradient(120deg, #f59e0b, #db2777); color: #fff; }
.plan-card__tag {
    font-size: var(--fs-xs); font-weight: 800; letter-spacing: .08em;
    color: #b45309;
}
.plan-card__lead { margin: 0 0 var(--s-3); color: var(--c-ink); font-size: var(--fs-base); }
.plan-card__lead:last-child { margin-bottom: 0; }
/* Breathing room between the card's CTA button and the usage tiles below. */
.plan-card form { margin-top: var(--s-4); margin-bottom: 0; }
.plan-card { margin-bottom: var(--s-6); }

/* Recent-activity list: fold long histories behind a "Show all" toggle. */
.runs-block .run-row--extra { display: none; }
.runs-toggle-input:checked ~ .account-table .run-row--extra { display: table-row; }
.runs-toggle {
    display: inline-block; margin-top: var(--s-4); cursor: pointer;
    color: var(--c-accent); font-weight: 700; font-size: var(--fs-sm);
    user-select: none;
}
.runs-toggle:hover { text-decoration: underline; }
.runs-toggle .runs-toggle__less { display: none; }
.runs-toggle-input:checked ~ .runs-toggle .runs-toggle__more { display: none; }
.runs-toggle-input:checked ~ .runs-toggle .runs-toggle__less { display: inline; }

/* Daily-AI usage meter (free tier). */
.usage-meter { margin: 0 0 var(--s-5); }
.usage-meter__bar {
    height: 8px; border-radius: var(--r-full); background: var(--c-surface-3);
    overflow: hidden; margin-bottom: var(--s-2);
}
.usage-meter__bar span {
    display: block; height: 100%;
    background: linear-gradient(90deg, var(--c-accent), #db2777);
}
.usage-meter__note { margin: 0; color: var(--c-muted); font-size: var(--fs-sm); }

/* "Pro" pill next to VIP settings headings. */
.badge--pro {
    background: linear-gradient(120deg, #f59e0b, #db2777); color: #fff;
    font-size: var(--fs-xs); font-weight: 800; letter-spacing: .04em;
    padding: 2px 8px; border-radius: var(--r-full); vertical-align: middle;
    text-transform: uppercase;
}

/* VIP logo swatches get a subtle gold ring so they read as premium. */
.avatar-choice--vip.is-selected { border-color: #f59e0b; }
.avatar-choice--vip:focus-within { border-color: #f59e0b; }

/* VIP profile banner (applied inline via style attr) — a touch taller. */
.profile-hero__banner--vip { height: 110px; }

/* VIP banner picker swatches. */
.banner-choices { display: flex; flex-wrap: wrap; gap: var(--s-3); margin-bottom: var(--s-3); }
.banner-choice {
    position: relative; cursor: pointer; border: 2px solid transparent;
    border-radius: var(--r-md); padding: 3px; display: grid; gap: 4px; justify-items: center;
}
.banner-choice.is-selected { border-color: #f59e0b; }
.banner-choice:focus-within { border-color: #f59e0b; }
.banner-choice input { position: absolute; opacity: 0; width: 0; height: 0; }
.banner-swatch {
    width: 96px; height: 40px; border-radius: var(--r-sm); border: 1px solid var(--c-line);
    display: block;
}
.banner-swatch--default { background: linear-gradient(120deg, var(--c-accent), #8b3fe0 55%, #db2777); }
.banner-choice__label { font-size: var(--fs-xs); color: var(--c-muted); font-weight: 600; }

/* ── Back to top ──────────────────────────────────────────────────────────────
   Floating button, revealed by core.js once the page has actually scrolled.

   Hidden here rather than in markup so the fade works both ways: `hidden` would
   have to fight this rule's `display`, and toggling it can't transition. With JS
   off the button simply never appears — better than a permanent floating button
   nothing can dismiss, and every tool on the site needs JS regardless.

   `visibility` (not opacity alone) is what keeps it out of the tab order and the
   accessibility tree while it's away. It transitions as a step that favours
   `visible`, so it fades in at once and only disappears after fading out.

   z-index 90 clears the sticky header (50) and header menus (60) but stays under
   the promo popup and camera overlay (200) — a modal must never be sat on. */
.to-top {
    position: fixed;
    right: var(--s-4);
    /* Clear of the iOS home strip / Android gesture bar where there is one. */
    bottom: calc(var(--s-4) + env(safe-area-inset-bottom, 0px));
    z-index: 90;
    width: 46px; height: 46px;
    display: grid; place-items: center;
    border-radius: var(--r-full);
    background: var(--c-accent); color: #fff;
    border: 1px solid color-mix(in srgb, #000 10%, var(--c-accent));
    box-shadow: var(--sh-lg);
    opacity: 0; visibility: hidden; transform: translateY(10px);
    transition: opacity .18s ease, transform .18s ease, visibility .18s, background-color .18s ease;
}
.to-top.is-visible { opacity: 1; visibility: visible; transform: none; }
.to-top:hover { background: var(--c-accent-dark); color: #fff; }
.to-top:focus-visible { outline: 3px solid var(--c-accent-soft); outline-offset: 2px; }
.to-top svg { display: block; }

@media (max-width: 560px) {
    /* Tighter margins on a phone, but still a 44px target. */
    .to-top {
        width: 44px; height: 44px; right: var(--s-3);
        bottom: calc(var(--s-3) + env(safe-area-inset-bottom, 0px));
    }
}
/* No reduced-motion rule needed: the global block above already applies
   `transition: none !important` to everything, which strips the fade and leaves
   the button snapping in and out. */
