/* ====================================================================
   public.css - the stylesheet for the pages a stranger can read. (WS-G1)

   IT DOES NOT LOAD app.css, AND THAT IS THE POINT.

   app.css is 1400 lines styling the member area: the sidebar, the topbar,
   the notification dropdown, the comment thread, the admin tables, the
   trip map, the lightbox. None of that markup exists on this surface. A
   public page that loaded it would ship the shape of the private site to
   every crawler that fetched a stylesheet, and would make "does this page
   render member chrome?" a question about which classes happened to be
   emitted rather than a question about which rules exist.

   WHAT IT DOES LOAD, FIRST, IS tokens.css.

   The roadmap asked this file to "open with the same :root block app.css
   and landing.css already duplicate". That duplication was REAL when the
   roadmap was written and WS-D1 removed it: tokens.css is now the single
   place every colour, size, space, duration and layer is named, and all
   four layouts link it before their own sheet. Re-creating the palette
   here to satisfy the letter of that instruction would put a fourth copy
   of the theme on the site and undo the work. So this file defines
   exactly two tokens of its own -- both genuinely local to this surface
   -- and speaks tokens.css's vocabulary for everything else.

   THE THEME IS REFINED, NEVER REPLACED. Bebas headings, Courier body,
   red rules, yellow links, square corners, corner brackets on panels.
   A stranger who lands here from a pasted link and a member who signs in
   are looking at the same site.

   CONTENTS
     1  LOCAL TOKENS + BASE          6  BROWSE GRID + CARD
     2  SKIP LINK                    7  FACETS + PAGINATION
     3  SHELL: MASTHEAD, FOOTER      8  FOCUS
     4  PREVIEW RIBBON               9  RESPONSIVE
     5  RECIPE PAGE                 10  MOTION
   ==================================================================== */


/* ====================================================================
   1. LOCAL TOKENS + BASE
   ==================================================================== */

:root {
    /* The reading column. Narrower than --measure-page (1400px, which is
       right for a dashboard full of grids) because this surface is one
       recipe read top to bottom, and 1400px of ingredient table is not a
       thing anybody wants. */
    --pub-measure: 60rem;

    /* The preview ribbon's height, named because both the ribbon and the
       masthead offset below it need the same number and a magic 2.5rem
       in two places is how they drift apart. */
    --pub-ribbon-h: 2.5rem;
}

* { box-sizing: border-box; }

body {
    background-color: var(--c-bg);
    color: var(--c-ink);
    font-family: var(--font-mono);
    font-size: var(--fs-400);
    line-height: var(--lh-base);
    margin: 0;
}

a { color: var(--c-yellow); text-decoration: none; transition: color var(--dur-slow) var(--ease); }
a:hover { color: var(--c-red); text-shadow: 0 0 5px var(--c-red); }

/* The element reset app.css section 1 established, restated for this
   sheet: without it every heading here carries whatever margin the user
   agent feels like, and the two halves of the site get different
   vertical rhythm again. */
h1, h2, h3 { font-family: var(--font-ui); line-height: var(--lh-tight); margin: 0 0 var(--space-4); }
p  { margin: 0 0 var(--space-4); }
ul, ol { margin: 0 0 var(--space-4); padding-left: var(--space-6); }
img { max-width: 100%; }

.pub-shell {
    max-width: var(--pub-measure);
    margin: 0 auto;
    padding: 0 var(--space-5);
}


/* ====================================================================
   2. SKIP LINK

   Copied from app.css section 2 rather than shared, because sharing it
   would mean loading app.css. Kept identical so the two halves of the
   site behave the same way for a keyboard user.
   ==================================================================== */

.skip-link {
    position: absolute;
    left: -9999px;
    top: 0;
    z-index: var(--z-toast);
    background: var(--c-red);
    color: #000;
    font-family: var(--font-ui);
    font-size: var(--fs-ui-300);
    letter-spacing: 1px;
    padding: var(--space-3) var(--space-5);
}
.skip-link:focus { left: var(--space-2); top: var(--space-2); color: #000; text-shadow: none; }


/* ====================================================================
   3. SHELL: MASTHEAD, FOOTER
   ==================================================================== */

.pub-masthead {
    border-bottom: 2px solid var(--c-red);
    background: var(--c-surface-1);
    padding: var(--space-4) 0;
    margin-bottom: var(--space-7);
}
.pub-masthead .pub-shell {
    display: flex;
    align-items: baseline;
    gap: var(--space-4);
    flex-wrap: wrap;
}

.pub-wordmark {
    font-family: var(--font-display);
    font-size: var(--fs-700);
    color: var(--c-red);
    letter-spacing: 2px;
}
.pub-wordmark:hover { color: var(--c-yellow); text-shadow: 0 0 8px var(--c-red); }

.pub-tagline {
    font-family: var(--font-ui);
    font-size: var(--fs-ui-200);
    color: var(--c-ink-3);
    letter-spacing: 3px;
}

.pub-footer {
    border-top: 1px solid var(--c-line);
    margin-top: var(--space-9);
    padding: var(--space-6) 0 var(--space-8);
    font-family: var(--font-ui);
    letter-spacing: 1px;
}
.pub-footer-note {
    color: var(--c-ink-4);
    font-family: var(--font-mono);
    font-size: var(--fs-200);
    letter-spacing: 0;
    margin-top: var(--space-3);
    max-width: var(--measure-prose);
}


/* ====================================================================
   4. PREVIEW RIBBON

   Fixed, so it overlays rather than reflows: the page underneath has to
   be the public render exactly, or the preview is a lie. It is the only
   element on this surface a stranger never receives, and it is emitted
   only under an 'auth' route.
   ==================================================================== */

.pub-preview-ribbon {
    position: fixed;
    inset: 0 0 auto 0;
    z-index: var(--z-toast);
    height: var(--pub-ribbon-h);
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--space-2);
    background: var(--c-yellow);
    color: #000;
    font-family: var(--font-ui);
    font-size: var(--fs-ui-200);
    letter-spacing: 2px;
    text-align: center;
    padding: 0 var(--space-4);
}
/* Only the previewing member's page is pushed down; nothing about the
   public render changes. */
.pub-preview-ribbon ~ .pub-masthead { margin-top: var(--pub-ribbon-h); }


/* ====================================================================
   5. RECIPE PAGE
   ==================================================================== */

.pub-recipe-head { margin-bottom: var(--space-7); }

/*
 * THE HERO IS THE CARD'S FRAME AT FULL SIZE.  (ST-3)
 *
 * A stranger arrives here by clicking a recon-framed card on /browse.
 * Landing on a plain photograph in a 2px grey box is the moment the two
 * pages stop being the same building. Same geometry, same film, same
 * vignette, same reticle -- browse.php and this page both link
 * recon-card.css, which owns .recon-frame and .recon-meta, and the only
 * thing that changes is the scale.
 *
 * 16/10 to match the card, and the derivative is 1600x1067 (3/2), so
 * object-fit: cover on .pub-hero-img is doing real work here and is
 * already declared below.
 */
.pub-hero {
    position: relative;

    /* 16/10 ALONE IS A TRAP ON A LAPTOP. At a 918px content width it resolves to a
       573px hero, which on a 1280x609 viewport is 94% of the screen: a stranger
       following a shared permalink landed on a wall of texture with the dish name,
       the threat readout and the first ingredient all below the fold.

       The cap wins because max-height beats the aspect ratio, so the frame stays
       16/10 until it would get too tall and then simply crops - object-fit: cover on
       .pub-hero-img is what makes that crop harmless. 46vh keeps the title and the
       threat chip on the first screen at every laptop height; the 430px ceiling stops
       a very tall monitor turning it back into a poster. */
    /* An explicit height rather than max-height, because aspect-ratio ties the two
       dimensions together: capping the height shrank the WIDTH to match and left the
       hero as a small box floating in the left of the column. Width comes from the
       container, height is clamped, and object-fit: cover on .pub-hero-img absorbs
       the difference. */
    width: 100%;
    height: clamp(210px, 42vh, 400px);
    overflow: hidden;
    border-radius: var(--radius-pane);
    border: 1px solid var(--edge-dim);
    border-top-color: var(--edge-e2);
    box-shadow: inset 0 1px 0 var(--gloss-e2), var(--shadow-e2);
    background: linear-gradient(158deg, #16202E 0%, #0E1220 62%, #090B13 100%);
    margin-bottom: var(--space-5);
}
.pub-hero-img { position: absolute; inset: 0; width: 100%; height: 100%; object-fit: cover; display: block; }

/* The film and the vignette, identical to the card's -- see the note on
   .recipe-thumb::after in recon-card.css. One pseudo-element carrying
   both, so the hero costs one extra paint layer and not two. */
.pub-hero::after {
    content: "";
    position: absolute;
    inset: 0;
    z-index: 2;
    pointer-events: none;
    background:
        linear-gradient(180deg,
            rgba(146, 180, 255, 0.10) 0%,
            transparent 24%,
            transparent 52%,
            rgba(6, 7, 12, 0.80) 100%),
        repeating-linear-gradient(0deg,
            rgba(0, 0, 0, 0.16) 0 1px,
            transparent 1px 3px);
}
/* The reticle is bigger here because the frame is. Everything else about
   it -- the phosphor, the single container-level glow, the corner
   geometry -- is recon-card.css's and is not restated. */
.pub-hero .recon-frame { inset: 16px; }
.pub-hero .recon-frame i { width: 22px; height: 22px; }
.pub-hero .recon-meta { left: 18px; bottom: 14px; }

/*
 * THE PAGE TITLE, ON BOTH PUBLIC PAGES.
 *
 * ST-3: the 2px hard black offset and the solid red underline are gone.
 * They were a flat-poster treatment on a surface that now has depth and
 * a light source — the offset reads as a sticker seam at this size, and
 * a 2px rule in classification red under every heading spends the
 * loudest colour in the palette on the most ordinary element on the page.
 *
 * The bloom is the reference's: a wide chrome halo, as if the type were
 * lit from behind, plus a 2px black shadow directly under it so the
 * letterforms still have an edge over a bright photograph. The rule
 * under it is depth.css's .rule gradient — classification cooling into
 * chrome and then into nothing — drawn as a border-image so it stays one
 * element.
 */
.pub-title {
    font-size: var(--fs-900);
    color: #fff;
    letter-spacing: 0.015em;
    text-shadow:
        0 0 34px rgba(var(--chrome-rgb), 0.34),
        0 2px 0 rgba(0, 0, 0, 0.6);
    border-bottom: 1px solid transparent;
    border-image: linear-gradient(
        90deg,
        rgba(var(--classify-rgb), 0.55),
        rgba(var(--chrome-rgb), 0.28),
        transparent) 1;
    padding-bottom: var(--space-3);
    margin-bottom: var(--space-3);
}

/* The published date. It is the ONLY provenance line on this page: the
   recipe says when it was released and nothing about who wrote it. */
.pub-dateline {
    font-family: var(--font-mono);
    font-size: var(--fs-200);
    color: var(--c-ink-3);
    letter-spacing: 1px;
}

.pub-heat { font-size: var(--fs-600); letter-spacing: 2px; }
.pub-heat-label {
    font-family: var(--font-ui);
    font-size: var(--fs-ui-100);
    color: var(--c-yellow);
    border: 1px solid var(--c-red-dim);
    padding: 2px 8px;
    letter-spacing: 2px;
    vertical-align: middle;
}

.pub-meta-bar {
    display: flex;
    gap: var(--space-6);
    flex-wrap: wrap;
    padding: var(--space-4) var(--space-5);
    background: var(--c-surface-1);
    border: var(--border-1);
    margin-bottom: var(--space-7);
    font-family: var(--font-mono);
    font-size: var(--fs-300);
}
.pub-meta-key   { color: var(--c-ink-4); }
.pub-meta-value { color: var(--c-yellow); }

.pub-desc {
    color: var(--c-ink-2);
    max-width: var(--measure-prose);
    line-height: var(--lh-prose);
    margin-bottom: var(--space-7);
}

/* The same shape as app.css's .section-title -- Bebas, white, red rule --
   under a name of its own so the two sheets cannot fight over one class
   if either is edited later. */
.pub-section-title {
    font-size: var(--fs-600);
    color: #fff;
    border-bottom: 2px solid var(--c-red);
    margin-bottom: var(--space-5);
    text-shadow: 2px 2px 0 #000;
}

.pub-block { margin-bottom: var(--space-8); }

.pub-ingredients { width: 100%; max-width: 40rem; border-collapse: collapse; }
.pub-ingredients th {
    font-family: var(--font-heading);
    letter-spacing: 2px;
    text-align: left;
    padding: var(--space-2);
    border-bottom: 2px solid var(--c-red);
    color: var(--c-ink);
}
.pub-ingredients td { padding: var(--space-2); border-bottom: 1px solid var(--c-line-3); }
.pub-ing-amount { color: var(--c-yellow); width: 5rem; }
.pub-ing-unit   { color: var(--c-ink-3);  width: 5rem; }
.pub-ing-name   { color: var(--c-ink-2); }

.pub-steps { list-style: none; padding: 0; margin: 0; counter-reset: pubstep; }
.pub-step {
    display: flex;
    gap: var(--space-4);
    margin-bottom: var(--space-6);
    padding-bottom: var(--space-6);
    border-bottom: 1px solid var(--c-line-3);
}
.pub-step:last-child { border-bottom: 0; }
.pub-step-n {
    flex-shrink: 0;
    width: 3rem; height: 3rem;
    background: var(--c-red);
    color: #fff;
    display: flex; align-items: center; justify-content: center;
    font-family: var(--font-heading);
    font-size: var(--fs-ui-400);
    /* The theme's angled block. See section 8: a clip-path clips an
       outline too, so this control's focus treatment is an inset ring. */
    clip-path: polygon(10% 0, 100% 0, 90% 100%, 0 100%);
}
.pub-step-body { flex: 1; color: var(--c-ink-2); line-height: var(--lh-prose); }

.pub-tags { display: flex; gap: var(--space-2); flex-wrap: wrap; }
.pub-tag {
    display: inline-block;
    background: var(--c-surface-3);
    border: var(--border-1);
    color: var(--c-yellow);
    padding: 4px 12px;
    font-family: var(--font-mono);
    font-size: var(--fs-100);
    letter-spacing: 1px;
}
.pub-tag:hover { border-color: var(--c-yellow); text-shadow: none; }


/* ====================================================================
   6. BROWSE GRID + CARD

   NOT a reuse of .recipe-card. views/partials/recipe_card.php prints
   `CHEF: <name>` in its meta line, and the public card has no meta line
   at all -- so the two have different markup and get different rules
   rather than one class that means two things.

   ##  ST-3: THE RULES BELOW ARE NOW THE FALLBACK, NOT THE FINISHED CARD  ##

   The finished card lives in public/css/recon-card.css, which
   views/public/browse.php links after this sheet, and which names BOTH
   class prefixes on every rule. That is not a retreat from the paragraph
   above -- the two MARKUPS stay two files and the split that matters is
   the split in the projection, which is unchanged. What was wrong is
   that two stylesheets were separately deciding what a card looks like,
   and the public one had quietly fallen a year behind: it still had the
   grayscale, the red line drawn across the bottom of the photograph and
   a heat row of chilli glyphs, on the one surface a stranger sees first.

   Everything here is kept and left working. If recon-card.css does not
   arrive, this page renders yesterday's card instead of an unstyled one,
   which is the property that makes linking a stylesheet from a view safe.
   ==================================================================== */

.pub-lede {
    color: var(--text-dim);
    max-width: var(--measure-prose);
    margin-bottom: var(--space-6);
}

/*
 * THE ONE NUMBER ON THIS PAGE THAT GLOWS.
 *
 * It is a COUNT(*) the server ran for this request over the anonymous
 * visibility floor — live system data, which is the whole of what
 * depth.css rule 3 hands the phosphor to. Everything else in the lede is
 * a sentence.
 */
.pub-count {
    font-family: var(--font-ui);
    font-size: var(--fs-ui-500);
    letter-spacing: 0.06em;
    color: var(--phosphor);
    text-shadow: var(--glow-phosphor);
    font-variant-numeric: tabular-nums;
}

.pub-grid {
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(16rem, 1fr));
    gap: var(--space-5);
    margin-bottom: var(--space-7);
}

.pub-card {
    background: var(--c-surface-1);
    border: var(--border-1);
    overflow: hidden;
    position: relative;
    transition: border-color var(--dur-slow) var(--ease);
}
.pub-card:hover { border-color: var(--c-yellow); }

.pub-card-thumb { position: relative; aspect-ratio: 3 / 2; overflow: hidden; background: var(--c-surface-1); }
.pub-card-img {
    position: absolute; inset: 0; width: 100%; height: 100%;
    object-fit: cover; display: block;
    border-bottom: 1px solid var(--c-red);
    filter: grayscale(40%);
    transition: filter var(--dur-slow) var(--ease);
}
.pub-card:hover .pub-card-img { filter: grayscale(0%); }

.pub-card-blank {
    position: absolute; inset: 0;
    display: flex; align-items: center; justify-content: center;
    border-bottom: 1px solid var(--c-red);
    color: var(--c-ink-4);
    font-family: var(--font-ui);
    font-size: var(--fs-ui-200);
    letter-spacing: 3px;
    text-align: center;
}

.pub-card-body { padding: var(--space-4); }
.pub-card-heat { font-size: var(--fs-300); margin-bottom: var(--space-2); }
.pub-card-title { font-size: var(--fs-ui-400); margin: 0 0 var(--space-2); }
.pub-card-excerpt { color: var(--c-ink-3); font-size: var(--fs-200); margin: 0; }

/* Whole-card clickability with one anchor, exactly as .recipe-link does
   it in app.css: an invisible stretched box in CSS, no JavaScript, and
   the accessible name of the link stays the recipe's title rather than
   the whole card read aloud as one link. */
.pub-card-link::after { content: ''; position: absolute; inset: 0; }

/*
 * THE FOCUS RING GOES ROUND THE CARD, and this is the fallback copy of
 * it -- recon-card.css draws the focused card in full, with the same
 * lift and light hover gets, so a keyboard user is shown the same object
 * in the same state a mouse user is.
 *
 * Without this the ring lands on the TITLE TEXT, two lines in the middle
 * of a card, while the thing that would actually be activated is the
 * whole card. The global :focus-visible in section 8 stays exactly where
 * it is and is never suppressed: if :has() is unavailable the ring gets
 * smaller, it does not disappear.
 */
.pub-card:has(.pub-card-link:focus-visible) {
    outline: 2px solid var(--chrome);
    outline-offset: 3px;
    border-color: rgba(var(--chrome-rgb), 0.45);
}


/* ====================================================================
   7. FACETS + PAGINATION

   The pagination classes are the ones views/partials/pagination.php
   emits. That partial is shared with the member index on purpose --
   there is one paging implementation on this site and this surface is
   not getting a second -- so its five class names are styled here as
   well as in app.css.
   ==================================================================== */

/*
 * ##  ST-3: THE RAIL IS TWO ROWS NOW, AND THE COUNTS ARE READOUTS.  ##
 *
 * It was twenty-seven identical chips in one alphabetical row, so
 * CHINESE sat between CHEAP and CHRISTMAS and nothing on screen said
 * that one of those three is a place and two of them are not. The view
 * splits them into the eleven cuisines and everything else; this draws
 * the two rows and the heading over each.
 *
 * .pub-facets is still ONE class doing both rows. They are the same
 * control offering the same links into the same query, and giving the
 * second row its own visual identity would say they were different
 * kinds of thing, which is exactly the mistake the split is fixing —
 * they are the same kind of thing under two headings.
 *
 * THE GROUP WRAPPER carries the rule under the whole rail, so the two
 * rows are visibly one object with a line under it rather than two
 * objects with a line each.
 */
.pub-facet-groups {
    padding-bottom: var(--space-5);
    margin-bottom: var(--space-7);
    border-bottom: 1px solid var(--edge-dim);
}

/*
 * .pub-facets IS ALSO THE RECIPE PAGE'S SCALE CONTROL — the 0.5x / 1x /
 * 2x / 4x row above the supply list is written in these same classes,
 * deliberately, so the public surface has one control vocabulary and not
 * two. So this rule keeps the standalone row's own rule and spacing, and
 * the group wrapper takes them back for the browse rail, where one line
 * under two rows is the whole point of having a wrapper.
 */
.pub-facets {
    display: flex;
    gap: var(--space-2);
    flex-wrap: wrap;
    align-items: center;
    padding-bottom: var(--space-3);
    margin-bottom: var(--space-5);
    border-bottom: 1px solid var(--edge-dim);
}
.pub-facet-groups .pub-facets {
    padding-bottom: 0;
    margin-bottom: 0;
    border-bottom: 0;
}
.pub-facet-groups .pub-facets--filed { margin-top: var(--space-4); }

/*
 * The heading over a row. A fixed width, IN THE GROUP ONLY, so the two
 * rows start their chips at the same x — a ragged left edge on two
 * stacked rails is most of what made the old one look like a spill
 * rather than a control. The scale control on the recipe page is one
 * row with a two-word label and wants no such column, which is why the
 * width is scoped and the type is not.
 */
.pub-facet-groups .pub-facet-label {
    flex: 0 0 auto;
    min-width: 12rem;
}
.pub-facet-label {
    font-family: var(--font-mono);
    font-size: var(--fs-100);
    letter-spacing: 0.2em;
    text-transform: uppercase;
    color: var(--text-faint);
}

.pub-facet {
    display: inline-flex;
    align-items: baseline;
    gap: var(--space-2);
    border-radius: var(--radius-1);
    border: 1px solid var(--edge-dim);
    border-top-color: var(--edge-e1);
    background: var(--pane-1-solid);
    color: var(--text-2);
    padding: 5px 12px;
    font-family: var(--font-ui);
    font-size: var(--fs-ui-100);
    letter-spacing: 0.06em;
    transition:
        border-color var(--dur-base) var(--ease),
        background   var(--dur-base) var(--ease),
        color        var(--dur-base) var(--ease);
    /* WCAG 2.5.5 — this rail is the whole navigation of the public site
       and it is used on a phone more than anywhere else. */
    min-height: 32px;
}
.pub-facet:hover {
    border-color: rgba(var(--chrome-rgb), 0.45);
    border-top-color: rgba(var(--chrome-rgb), 0.55);
    background: var(--pane-2-solid);
    color: var(--chrome);
    text-shadow: none;
}
/* Element-qualified, and that is the point: .pub-facet is worn by links,
   by the recipe page's scale BUTTONS, and by a plain <span> for the one
   you are already on. Only two of those three are things you can press,
   and a hand cursor over the third says otherwise. */
a.pub-facet,
button.pub-facet { cursor: pointer; }

/*
 * THE COUNT, AND THIS IS WHERE THE WEIGHT WENT.
 *
 * It used to be "(11)" in the same face, size and colour as the name,
 * which is a number you have to stop and parse. It is now a readout:
 * monospace and tabular so a column of them lines up, phosphor because
 * it is a COUNT(*) the server computed for this request, and behind a
 * hairline so it reads as a separate field of the chip rather than as
 * part of the word.
 *
 * IT DOES NOT GLOW. Rule 3 says live data may; it does not say all of it
 * must, and twenty-seven glowing numbers is a rail where nothing is
 * urgent. The one on the facet you are standing in does — see below —
 * which is the whole point of a signal.
 */
.pub-facet-n {
    padding-left: var(--space-2);
    border-left: 1px solid var(--edge-dim);
    font-family: var(--font-mono);
    font-size: var(--fs-100);
    font-variant-numeric: tabular-nums;
    letter-spacing: 0;
    color: var(--phosphor);
    opacity: 0.85;
}

/* ALL has no count — it is not a tag and it has nothing to count — so it
   gets the hairline treatment on the other side to sit level with the
   chips it leads. */
.pub-facet--all { font-family: var(--font-mono); letter-spacing: 0.16em; }

/*
 * WHERE YOU ARE. A filled chip in classification red was the old answer
 * and it is too loud for "you are on this page" — that colour means a
 * classification on every other surface of this site. Chrome fill,
 * chrome edge, and the count inside it finally allowed to glow.
 */
.pub-facet.is-current {
    background: rgba(var(--chrome-rgb), 0.12);
    border-color: rgba(var(--chrome-rgb), 0.5);
    border-top-color: rgba(var(--chrome-rgb), 0.65);
    color: #fff;
    box-shadow:
        inset 0 1px 0 var(--gloss-e2),
        0 0 26px -12px rgba(var(--chrome-rgb), 0.7);
}
.pub-facet.is-current:hover { color: #fff; }
.pub-facet.is-current .pub-facet-n {
    opacity: 1;
    border-left-color: rgba(var(--chrome-rgb), 0.35);
    text-shadow: var(--glow-phosphor);
}

.pub-facet:focus-visible { outline-offset: 3px; }

.pub-empty {
    border: 1px solid var(--edge-dim);
    border-top-color: var(--edge-e1);
    border-radius: var(--radius-pane);
    background: var(--pane-1-solid);
    box-shadow: inset 0 1px 0 var(--gloss-e1), var(--shadow-e1);
    padding: var(--space-9) var(--space-7);
    text-align: center;
    color: var(--text-dim);
    font-family: var(--font-ui);
    font-size: var(--fs-ui-300);
    letter-spacing: 0.2em;
    line-height: 1.9;
}

.btn-small {
    background: var(--c-surface-2); border: 1px solid var(--c-line-2); color: var(--c-ink-2);
    padding: 5px 10px; font-family: var(--font-ui); display: inline-block; position: relative;
}
.btn-small:hover { border-color: var(--c-yellow); color: var(--c-yellow); text-shadow: none; }

.pagination {
    display: flex; justify-content: center; align-items: center;
    gap: var(--space-2); flex-wrap: wrap;
    margin-top: var(--space-5); padding-top: var(--space-2);
    border-top: 1px dashed var(--c-line);
}
.pagination-status { margin-right: auto; color: var(--c-ink-4); font-size: var(--fs-200); }
.page-current { background: var(--c-red); border-color: var(--c-red); color: #000; }
.page-current:hover { border-color: var(--c-red); color: #000; }
.page-gap { color: var(--c-ink-4); padding: 0 var(--space-1); }


/* ====================================================================
   8. FOCUS

   One global rule, same yellow ring and same :focus-visible (not
   :focus) as app.css section 22, so a keyboard user gets the identical
   affordance on both halves of the site.

   Note what is deliberately NOT in this file anywhere: `outline: none`.
   Removing the only focus indicator a control has is the bug WS-D3
   spent a section undoing, and 025_theme_contract_test.php refuses any
   stylesheet in this directory that contains one.
   ==================================================================== */

:focus-visible {
    outline: 2px solid var(--c-yellow);
    outline-offset: 2px;
}

/* clip-path clips the outline away, so the one clipped element on this
   surface gets an inset ring instead of an invisible one. */
.pub-step-n:focus-visible { box-shadow: inset 0 0 0 2px var(--c-yellow); }


/* ====================================================================
   9. RESPONSIVE

   This surface is read on a phone more than anywhere else on the site:
   it is the page a pasted link opens. One breakpoint, because there is
   one column and it only has to stop being cramped.
   ==================================================================== */

@media (max-width: 40rem) {
    .pub-shell { padding: 0 var(--space-4); }
    .pub-title { font-size: var(--fs-700); }
    .pub-meta-bar { gap: var(--space-4); padding: var(--space-3) var(--space-4); }
    .pub-grid { grid-template-columns: repeat(auto-fill, minmax(12rem, 1fr)); }
    .pub-step { gap: var(--space-3); }
    .pub-step-n { width: 2.25rem; height: 2.25rem; font-size: var(--fs-ui-200); }
    .pub-preview-ribbon { font-size: var(--fs-100); letter-spacing: 1px; }

    /* The label's column is worth nothing once the chips wrap anyway, and
       a 12rem block on a 20rem screen is one heading and one chip per
       line. It becomes a full-width heading with the row under it. */
    .pub-facet-groups .pub-facet-label {
        flex: 1 0 100%;
        min-width: 0;
        margin-bottom: var(--space-1);
    }
    .pub-empty { padding: var(--space-7) var(--space-4); letter-spacing: 0.12em; }
}


/* ====================================================================
   10. MOTION
   ==================================================================== */

@media (prefers-reduced-motion: reduce) {
    *, *::before, *::after {
        animation-duration: 0.01ms !important;
        animation-iteration-count: 1 !important;
        transition-duration: 0.01ms !important;
        scroll-behavior: auto !important;
    }
}
