@font-face {
    font-family: 'JetBrains Mono';
    src: url('fonts/JetBrainsMono.woff2') format('woff2');
    font-weight: 100 800;
    font-style: normal;
    font-display: swap;
}

:root {
    --bg: #eef0f2;
    --card-bg: #fbfcfd;
    --border: rgba(0, 0, 0, .15);
    --border-soft: rgba(0, 0, 0, .08);
    --text: #17191c;
    --text-muted: rgba(0, 0, 0, .4);
    --text-faint: rgba(0, 0, 0, .35);
    --text-soft: rgba(0, 0, 0, .65);
    --text-row: rgba(0, 0, 0, .75);
    --warn: rgba(0, 0, 0, .25);
    --items-bg: rgba(0, 0, 0, .02);
    --items-border: rgba(0, 0, 0, .1);
    --accent-dot: #1467d6;
    --error: #b3261e;
    color-scheme: light;
}

/* Dark: same man-page character, inverted — cool graphite paper,
   pale ink, bright signal dot. Applied by system preference below, or
   explicitly via data-theme (set by the header toggle). */
:root[data-theme="dark"] {
    --bg: #15171a;
    --card-bg: #1e2126;
    --border: rgba(255, 255, 255, .18);
    --border-soft: rgba(255, 255, 255, .09);
    --text: #dfe4e9;
    --text-muted: rgba(223, 228, 233, .5);
    --text-faint: rgba(223, 228, 233, .42);
    --text-soft: rgba(223, 228, 233, .7);
    --text-row: rgba(223, 228, 233, .8);
    --warn: rgba(223, 228, 233, .32);
    --items-bg: rgba(255, 255, 255, .025);
    --items-border: rgba(255, 255, 255, .12);
    --accent-dot: #58a6ff;
    --error: #ff7066;
    color-scheme: dark;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme="light"]) {
        --bg: #15171a;
        --card-bg: #1e2126;
        --border: rgba(255, 255, 255, .18);
        --border-soft: rgba(255, 255, 255, .09);
        --text: #dfe4e9;
        --text-muted: rgba(223, 228, 233, .5);
        --text-faint: rgba(223, 228, 233, .42);
        --text-soft: rgba(223, 228, 233, .7);
        --text-row: rgba(223, 228, 233, .8);
        --warn: rgba(223, 228, 233, .32);
        --items-bg: rgba(255, 255, 255, .025);
        --items-border: rgba(255, 255, 255, .12);
        --accent-dot: #58a6ff;
        --error: #ff7066;
        color-scheme: dark;
    }
}

* {
    box-sizing: border-box;
}

body {
    margin: 0 auto;
    padding: 18px;
    max-width: 460px;
    background: var(--bg);
    color: var(--text);
    font-family: 'JetBrains Mono', ui-monospace, Menlo, monospace;
    -webkit-text-size-adjust: 100%;
}

header {
    display: flex;
    align-items: baseline;
    justify-content: space-between;
    padding-bottom: 12px;
    border-bottom: 2px solid var(--text);
}

h1 {
    margin: 0;
    font-size: 16px;
    font-weight: 700;
    letter-spacing: .5px;
}

a {
    color: var(--text);
}

.header-actions {
    display: flex;
    align-items: center;
    gap: 8px;
}

.button {
    display: inline-block;
    font-size: 11px;
    font-family: inherit;
    color: var(--text);
    background: none;
    border: 1px solid var(--text);
    padding: 5px 9px;
    text-decoration: none;
    cursor: pointer;
    line-height: normal;
}

.empty {
    color: var(--text-muted);
    text-align: center;
    padding: 3rem 0;
    font-size: 13px;
}

.pitch {
    color: var(--text-soft);
    font-size: 13px;
    line-height: 1.6;
    margin: 24px 0 0;
}

.reveal-warning {
    margin: 24px 0 0;
    padding: 10px 12px;
    border: 1px solid var(--text);
    font-size: 12px;
    line-height: 1.6;
}

.reveal-url-box {
    display: flex;
    align-items: stretch;
    gap: 0;
    margin: 14px 0 0;
    border: 1px solid var(--border);
    background: var(--card-bg);
}

.reveal-url {
    flex: 1;
    min-width: 0;
    padding: 12px;
    font-family: inherit;
    font-size: 12px;
    word-break: break-all;
    color: var(--text);
}

.reveal-url-box .button {
    flex: none;
    border: none;
    border-left: 1px solid var(--border);
}

.reveal-continue {
    text-align: center;
    margin: 24px 0 0;
    font-size: 11px;
}

.reveal-continue a {
    color: var(--text-muted);
    text-decoration: none;
}

/* Post feed (2026-11-07): one full-screen snap card per post from an
   updated site, newest first, swiped through like a reels feed. Layout
   tokens live here rather than in the color blocks at the top. */

:root {
    --header-h: 56px;
}

/* index.blade.php only: the header sticks, and every card is sized to
   "viewport minus header", so it has to be a known height. */
body:has(> header.bar) {
    padding-top: 0;
}

header.bar {
    position: sticky;
    top: 0;
    z-index: 2;
    height: var(--header-h);
    padding-bottom: 0;
    align-items: center;
    background: var(--bg);
}

/* Snap only when there are cards to snap between (not the all-quiet
   bookmark list). The root element is the scroller iOS Safari snaps. */
:root:has(.post) {
    scroll-snap-type: y mandatory;
    scroll-padding-top: calc(var(--header-h) + 12px);
}

/* ...and scroll freely while reading an expanded post or browsing the
   open drawer. Both can be taller than the screen, and mandatory snapping
   inside an oversized area is where iOS is least predictable. */
:root:has(.post.expanded),
:root:has(.quiet[open]:not(.bare)) {
    scroll-snap-type: none;
}

.feed {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin-top: 12px;
}

.feed[hidden] {
    display: none;
}

/* Cards size to their content, capped at the viewport under the header
   minus a 12px gap above and 28px below — so a long post fills the screen
   with ~16px of the next card peeking in as the "swipe up" hint, and a
   short one doesn't (2026-11-07, user: full-height cards for a two-line
   summary were mostly empty space). svh, not dvh: dvh changes as Safari's
   toolbars collapse mid-scroll, which would resize cards while snapping.
   vh line is the fallback. */
.post {
    scroll-snap-align: start;
    display: flex;
    flex-direction: column;
    max-height: calc(100vh - var(--header-h) - 40px);
    max-height: calc(100svh - var(--header-h) - 40px);
    border: 1px solid var(--border);
    background: var(--card-bg);
}

/* Only a screen-filling (clipped) card forces a stop on every swipe.
   Short cards can be flicked past several at a time — otherwise a run of
   summary-only posts would take one swipe per card, three to a screen. */
.post.overflows {
    scroll-snap-stop: always;
}

.post.expanded {
    max-height: none;
}

.post-site {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 11px 12px;
    text-decoration: none;
    border-bottom: 1px solid var(--border-soft);
}

.post-site .title {
    font-size: 12.5px;
    font-weight: 600;
    color: var(--text-soft);
}

.post-title {
    margin: 0;
    padding: 14px 12px 0;
    font-size: 17px;
    line-height: 1.35;
    font-weight: 700;
    overflow-wrap: break-word;
}

.post-title a {
    text-decoration: none;
}

/* Content-sized (basis auto), shrinking only once the card hits its max
   height — that shrink is what clips it and sets .overflows. Not flex: 1:
   a 0% basis in an auto-height column container is the kind of thing
   Safari has resolved differently from Chrome. */
.post-body {
    position: relative;
    flex: 0 1 auto;
    min-height: 0;
    overflow: hidden;
    padding: 0 12px;
}

/* .overflows is set by pulse.js once the body is measurably clipped. */
.post.overflows:not(.expanded) .post-body::after {
    content: '';
    position: absolute;
    left: 0;
    right: 0;
    bottom: 0;
    height: 64px;
    background: linear-gradient(to bottom, transparent, var(--card-bg));
    pointer-events: none;
}

.post-content {
    padding: 12px 0 2px;
    font-size: 13.5px;
    line-height: 1.65;
    color: var(--text-row);
    overflow-wrap: break-word;
}

.post-content > :first-child {
    margin-top: 0;
}

.post-content :is(p, ul, ol, dl, blockquote, pre, figure, table) {
    margin: 0 0 12px;
}

.post-content :is(h1, h2, h3, h4, h5, h6) {
    margin: 18px 0 8px;
    font-size: 14px;
    line-height: 1.4;
    color: var(--text);
}

.post-content :is(ul, ol) {
    padding-left: 20px;
}

.post-content a {
    color: var(--text);
}

.post-content img {
    max-width: 100%;
    height: auto;
    vertical-align: middle;
}

/* A loose top-level image (Ghost feeds lead with the feature image bare;
   WordPress often wraps one in a link) isn't covered by the block-margin
   rule above, so it sat flush against the next paragraph. Images inside
   text stay inline. */
.post-content > img,
.post-content > a > img {
    display: block;
    margin-bottom: 12px;
}

.post-content figure {
    margin-left: 0;
    margin-right: 0;
}

.post-content figcaption {
    margin-top: 4px;
    font-size: 11px;
    color: var(--text-muted);
}

.post-content blockquote {
    padding-left: 12px;
    border-left: 2px solid var(--border);
    color: var(--text-soft);
}

.post-content pre {
    overflow-x: auto;
    padding: 10px;
    background: var(--items-bg);
    border: 1px solid var(--items-border);
    font-size: 12px;
    line-height: 1.5;
}

.post-content code {
    font-family: inherit;
    font-size: .92em;
}

.post-content table {
    display: block;
    overflow-x: auto;
    border-collapse: collapse;
    font-size: 12px;
}

.post-content :is(td, th) {
    padding: 4px 6px;
    border: 1px solid var(--items-border);
}

.post-content hr {
    border: none;
    border-top: 1px solid var(--border-soft);
}

.post-foot {
    display: flex;
    border-top: 1px solid var(--border-soft);
    font-size: 11px;
}

/* An expanded full-content post can run to many screens; keep "show
   less" and "open on site" in reach the whole way down. */
.post.expanded .post-foot {
    position: sticky;
    bottom: 0;
    background: var(--card-bg);
}

/* Each control is a click target filling its whole share of the footer:
   half each when both show, the full row when "keep reading" is hidden
   (a short or body-less post). The padding lives on the controls rather
   than the footer so there's no dead strip around them, and the divider
   is on .more so it disappears with it. */
.post-foot .more,
.post-foot .open {
    flex: 1 1 0;
    min-width: 0;
    margin: 0;
    padding: 12px;
    font-family: inherit;
    font-size: 11px;
    line-height: 1.4;
    color: var(--text-soft);
}

.post-foot .more {
    border: none;
    border-right: 1px solid var(--border-soft);
    background: none;
    text-align: left;
    cursor: pointer;
}

.post-foot .open {
    text-align: right;
    text-decoration: none;
}

/* The button's visibility and label are driven by the card's classes
   rather than toggled on the button itself, so pulse.js has exactly one
   place to keep in step (pulseSyncPosts re-measures after a refresh). */
.post:not(.overflows):not(.expanded) .more,
.post.expanded .more-open,
.post:not(.expanded) .more-close {
    display: none;
}

/* A site you clicked through to this session: left in place, so nothing
   moves under your thumb, just visibly done. Gone on the next render. */
.post.seen {
    opacity: .55;
}

.post.seen .dot {
    visibility: hidden;
}

/* Last snap screen: the end-of-feed line, the quiet drawer, and the
   footer pinned to the bottom. Sized so its top snaps under the header
   and its bottom meets the body's 18px bottom padding exactly. */
:root:has(.post) .tail {
    scroll-snap-align: start;
    /* Same 12px gap as between cards — without it, the last card's bottom
       edge shows as a sliver under the header on this screen. */
    margin-top: 12px;
    display: flex;
    flex-direction: column;
    min-height: calc(100vh - var(--header-h) - 30px);
    min-height: calc(100svh - var(--header-h) - 30px);
}

:root:has(.post) .tail .identity {
    margin-top: auto;
}

/* Shared row atoms (post cards, quiet rows, manage rows) */

.dot {
    flex: none;
    width: 6px;
    height: 6px;
    border-radius: 50%;
    background: var(--accent-dot);
}

.favicon {
    flex: none;
    width: 20px;
    height: 20px;
    border-radius: 5px;
    object-fit: contain;
}

.favicon.chip {
    display: flex;
    align-items: center;
    justify-content: center;
    font-size: 11px;
    font-weight: 700;
}

.title {
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.warn {
    flex: none;
    color: var(--warn);
    font-size: 11px;
    cursor: help;
}

.time {
    flex: none;
    margin-left: auto;
    font-size: 11px;
    color: var(--text-muted);
}

/* Quiet shelf */

.all-quiet {
    margin: 18px 0 0;
    text-align: center;
    font-size: 11px;
    color: var(--text-muted);
    letter-spacing: .5px;
}

.quiet {
    margin-top: 18px;
}

.quiet summary {
    list-style: none;
    display: flex;
    align-items: center;
    gap: 6px;
    font-size: 10.5px;
    color: var(--text-muted);
    letter-spacing: .5px;
    cursor: pointer;
    user-select: none;
    -webkit-user-select: none;
}

.quiet summary::-webkit-details-marker {
    display: none;
}

.quiet summary::before {
    content: '▸';
}

.quiet[open] summary::before {
    content: '▾';
}

/* Nothing updated: no disclosure to disclose, so the drawer sheds its
   label/triangle and reads as a plain list of every site. */
.quiet.bare {
    margin-top: 4px;
}

.quiet.bare > summary {
    display: none;
}

.quiet-list,
.manage-list {
    list-style: none;
    margin: 8px 0 0;
    padding: 0;
    border: 1px solid var(--border);
    background: var(--card-bg);
}

.manage-list {
    margin-top: 18px;
}

.quiet-list > li,
.manage-list > li {
    display: flex;
    align-items: center;
}

.quiet-list > li + li,
.manage-list > li + li {
    border-top: 1px solid var(--border-soft);
}

.quiet-list .site-link,
.manage-list .site-link {
    flex: 1;
    min-width: 0;
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 11px 12px;
    font-size: 13.5px;
    color: var(--text-row);
    text-decoration: none;
}

.quiet-list .favicon,
.manage-list .favicon {
    width: 18px;
    height: 18px;
    border-radius: 4px;
}

.quiet-list .favicon.chip,
.manage-list .favicon.chip {
    font-size: 10px;
}

.remove {
    display: flex;
    align-self: stretch;
}

.remove button {
    border: none;
    background: none;
    color: var(--text-faint);
    font-size: 12px;
    padding: 0 12px;
    cursor: pointer;
    font-family: inherit;
}

/* Footer + forms */

.section-heading {
    margin: 32px 0 10px;
    padding-top: 14px;
    border-top: 1px solid var(--border-soft);
    font-size: 10.5px;
    font-weight: 600;
    letter-spacing: .8px;
    text-transform: uppercase;
    color: var(--text-muted);
}

.refresh {
    margin: 18px 0 4px;
}

.refresh button,
.add button {
    display: block;
    width: 100%;
    border: 1px solid var(--text);
    background: none;
    color: var(--text);
    font-family: inherit;
    font-size: 11px;
    padding: 9px;
    text-align: center;
    cursor: pointer;
}

.add {
    display: flex;
    flex-direction: column;
    gap: 10px;
    margin-top: 18px;
}

.add label {
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .5px;
    color: var(--text-muted);
}

.add input {
    padding: 10px 12px;
    font-family: inherit;
    font-size: 13.5px;
    border: 1px solid var(--border);
    background: var(--card-bg);
    color: var(--text);
}

.hint {
    color: var(--text-faint);
    font-size: 10.5px;
    margin: 0;
}

.error {
    margin-top: 18px;
    padding: 10px 12px;
    border: 1px solid var(--error);
    background: var(--card-bg);
    color: var(--error);
    font-size: 11.5px;
}

/* Manage page's account section: two adjacent single-button forms
   ("lock manage on this browser", "get a new secret url"). Without a
   container they sit flush against each other and read as one control. */
.account-actions {
    display: flex;
    flex-wrap: wrap;
    gap: 8px;
    margin-top: 10px;
}

.identity {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: 14px;
    flex-wrap: wrap;
    margin: 32px 0 0;
    padding-top: 14px;
    border-top: 1px solid var(--border-soft);
    color: var(--text-faint);
    font-size: 10.5px;
}

.identity a {
    border: none;
    background: none;
    color: var(--text-faint);
    font-family: inherit;
    font-size: 10.5px;
    text-decoration: underline;
    cursor: pointer;
    padding: 0;
}
