/* /Components/Admin/BannerPicker.razor.rz.scp.css */
/* The banner panel (spec §6.2): thumbnail, name and the three controls. Crimson marks the
   interactive controls (act); surfaces use the shared tokens (spec §3.1). */
.banner-picker[b-azrzyncyay] {
    display: flex;
    flex-direction: column;
    gap: 8px;
    font-family: var(--font-ui);
}

.banner-picker__title[b-azrzyncyay] {
    margin: 0;
    font-family: var(--font-serif);
    font-size: 17px;
    font-weight: 600;
    color: var(--text);
}

.banner-picker__current[b-azrzyncyay] {
    display: flex;
    align-items: center;
    gap: 12px;
    min-width: 0;
}

.banner-picker__thumb[b-azrzyncyay] {
    width: 120px;
    height: 68px;
    border-radius: 6px;
    object-fit: cover;
}

.banner-picker__name[b-azrzyncyay] {
    margin: 0;
    min-width: 0;
    overflow-wrap: anywhere;
    color: var(--muted);
    font-size: 13px;
}

.banner-picker__actions[b-azrzyncyay] {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}

.banner-picker__button[b-azrzyncyay] {
    padding: 4px 10px;
    border: 1px solid var(--muted);
    border-radius: 4px;
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: 12.5px;
    line-height: 1.5;
    cursor: pointer;
}

.banner-picker__button:hover[b-azrzyncyay] {
    border-color: var(--primary);
    color: var(--primary);
}

.banner-picker__button:focus-visible[b-azrzyncyay] {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
}

/* The transparent file input stretched over the "Upload" label: the native picker opens on
   click and on keyboard activation. */
.banner-picker__upload[b-azrzyncyay] {
    position: relative;
    display: inline-flex;
}

.banner-picker__file[b-azrzyncyay] {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
}
/* /Components/Admin/CategoryIcon.razor.rz.scp.css */
svg[b-lo3b9a0ihd] {
    display: inline-block;
    vertical-align: middle;
    flex: none;
}
/* /Components/Admin/DraftRestorePrompt.razor.rz.scp.css */
/* The restore prompt: the editor's ask before anything touches the text. The actions look like
   the form's action buttons; the panel is the surface's own card, in the theme's tokens (no
   inline style attribute — the enforced CSP). */
.draft-restore[b-7wwtc5dvip] {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    padding: 10px 12px;
    border: 1px solid var(--primary);
    border-radius: 6px;
    background: var(--surface);
    font-family: var(--font-ui);
}

.draft-restore__text[b-7wwtc5dvip] {
    flex: 1 1 260px;
    min-width: 0;
    margin: 0;
    color: var(--text);
    font-size: 13.5px;
}

.draft-restore__actions[b-7wwtc5dvip] {
    display: flex;
    gap: 6px;
}

.draft-restore__restore[b-7wwtc5dvip],
.draft-restore__discard[b-7wwtc5dvip] {
    padding: 5px 12px;
    border: 1px solid var(--primary);
    border-radius: 4px;
    background: var(--surface);
    color: var(--primary);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}

.draft-restore__restore:hover[b-7wwtc5dvip],
.draft-restore__discard:hover[b-7wwtc5dvip] {
    background: var(--primary-strong);
    color: var(--surface);
}

.draft-restore__restore:focus-visible[b-7wwtc5dvip],
.draft-restore__discard:focus-visible[b-7wwtc5dvip] {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
}
/* /Components/Admin/FeedbackPanel.razor.rz.scp.css */
/* The feedback notes panel (spec §6.2): quote cards, an optional add form and the collapsed
   resolved group. Crimson marks the actions (act); surfaces use the shared tokens. */
.feedback-panel[b-d8bvc1wosc] {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
    font-family: var(--font-ui);
    font-size: 14px;
}

.feedback-panel__add[b-d8bvc1wosc] {
    display: flex;
    flex-direction: column;
    gap: 6px;
}

.feedback-panel__label[b-d8bvc1wosc] {
    color: var(--muted);
    font-size: 12.5px;
    font-weight: 600;
}

.feedback-panel__add textarea[b-d8bvc1wosc] {
    padding: 6px 8px;
    border: 1px solid var(--muted);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: 13.5px;
    resize: vertical;
}

.feedback-panel__button[b-d8bvc1wosc],
.feedback-note__resolve[b-d8bvc1wosc] {
    align-self: flex-start;
    padding: 4px 10px;
    border: 1px solid var(--muted);
    border-radius: 4px;
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: 12.5px;
    cursor: pointer;
}

.feedback-panel__button:hover[b-d8bvc1wosc],
.feedback-note__resolve:hover[b-d8bvc1wosc] {
    border-color: var(--primary);
    color: var(--primary);
}

.feedback-panel__open[b-d8bvc1wosc],
.feedback-panel__resolved ul[b-d8bvc1wosc] {
    display: flex;
    flex-direction: column;
    gap: 12px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.feedback-panel__empty[b-d8bvc1wosc] {
    margin: 0;
    color: var(--muted);
}

.feedback-note[b-d8bvc1wosc] {
    padding: 10px 12px;
    border: 1px solid var(--muted);
    border-radius: 6px;
    background: var(--surface);
}

.feedback-note__quote[b-d8bvc1wosc] {
    margin: 0 0 6px;
    padding-left: 10px;
    border-left: 3px solid var(--secondary);
    color: var(--muted);
    font-family: var(--font-serif);
    font-style: italic;
}

.feedback-note__comment[b-d8bvc1wosc] {
    margin: 0 0 6px;
    color: var(--text);
    overflow-wrap: anywhere;
}

.feedback-note__author[b-d8bvc1wosc] {
    margin: 0 0 8px;
    color: var(--muted);
    font-size: 12px;
}

.feedback-panel__resolved summary[b-d8bvc1wosc] {
    cursor: pointer;
    color: var(--muted);
    font-size: 13px;
    font-weight: 600;
}

.feedback-panel__resolved ul[b-d8bvc1wosc] {
    margin-top: 12px;
}
/* /Components/Admin/IconPicker.razor.rz.scp.css */
.icon-picker[b-xxvrhyladn] {
    display: grid;
    gap: .75rem;
    min-width: 0;
}

/* The pinned default option and the grid tiles share one tile look, selected = crimson (act). */
.icon-picker__tile[b-xxvrhyladn] {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: .35rem;
    height: 76px;
    padding: .35rem .25rem;
    border: 1px solid transparent;
    border-radius: 8px;
    background: var(--surface);
    color: var(--text);
    font-family: var(--font-ui);
    cursor: pointer;
}

.icon-picker__tile:hover[b-xxvrhyladn] {
    background: var(--primary-container);
}

.icon-picker__tile:focus-visible[b-xxvrhyladn] {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.icon-picker__tile--selected[b-xxvrhyladn] {
    border-color: var(--primary);
    background: var(--primary-container);
}

.icon-picker__tile--default[b-xxvrhyladn] {
    width: 100%;
}

.icon-picker__name[b-xxvrhyladn] {
    max-width: 100%;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
    font-size: .72rem;
    color: var(--muted);
}

.icon-picker__grid[b-xxvrhyladn] {
    height: 360px;
    overflow-y: auto;
}

.icon-picker__row[b-xxvrhyladn] {
    display: flex;
    gap: .35rem;
    height: 84px;
    padding: 4px 0;
    box-sizing: border-box;
}

.icon-picker__row .icon-picker__tile[b-xxvrhyladn] {
    flex: 1 1 0;
    min-width: 0;
}
/* /Components/Admin/IconPickerDialog.razor.rz.scp.css */
/* The dialog needs room for the virtualised icon grid (8 tiles per row) without stretching to
   the full screen on wide monitors. */
.icon-picker-dialog[b-6isyyaffem] {
    min-width: min(640px, 80vw);
}
/* /Components/Admin/MarkdownEditor.razor.rz.scp.css */
/* The markdown editor (spec §6.2): toolbar controls and a mono textarea. Crimson marks the
   interactive controls (act); the surfaces use the shared tokens (spec §3.1). */
.md-editor[b-293b75xr78] {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
    font-family: var(--font-ui);
}

.md-editor__toolbar[b-293b75xr78] {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    align-items: center;
}

.md-editor__tool[b-293b75xr78] {
    padding: 4px 10px;
    border: 1px solid var(--muted);
    border-radius: 4px;
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: 12.5px;
    line-height: 1.5;
    cursor: pointer;
}

.md-editor__tool:hover[b-293b75xr78] {
    border-color: var(--primary);
    color: var(--primary);
}

.md-editor__tool:focus-visible[b-293b75xr78] {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
}

/* "Insert image" is a label with a transparent file input stretched over it: the native picker
   opens on click and on keyboard activation, with no script involved. */
.md-editor__insert[b-293b75xr78] {
    position: relative;
    display: inline-flex;
}

.md-editor__file[b-293b75xr78] {
    position: absolute;
    inset: 0;
    opacity: 0;
    cursor: pointer;
}

.md-editor__textarea[b-293b75xr78] {
    width: 100%;
    min-height: 320px;
    padding: 10px 12px;
    border: 1px solid var(--muted);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
    font-family: var(--font-mono);
    font-size: 14px;
    line-height: 1.55;
    resize: vertical;
}

.md-editor__textarea:focus-visible[b-293b75xr78] {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
}

/* The character count (over 90% of the limit) and the over-limit error below the textarea:
   quiet UI text; the error carries role="alert" so it announces itself. */
.md-editor__count[b-293b75xr78] {
    margin: 0;
    color: var(--muted);
    font-family: var(--font-ui);
    font-size: 12.5px;
    line-height: 1.5;
}

.md-editor__over[b-293b75xr78] {
    margin: 0;
    padding: 6px 10px;
    border: 1px solid var(--primary);
    border-radius: 6px;
    background: var(--primary-container);
    color: var(--text);
    font-family: var(--font-ui);
    font-size: 12.5px;
    line-height: 1.5;
}
/* /Components/Admin/MediaGrid.razor.rz.scp.css */
.media-grid[b-3ucha8883x] {
    display: grid;
    gap: 1.5rem;
}

.media-grid__toolbar[b-3ucha8883x] {
    display: flex;
    align-items: center;
    gap: 1rem;
    flex-wrap: wrap;
}

.media-grid__search[b-3ucha8883x] {
    min-width: 220px;
}

/* The drop zone doubles as the file picker: the invisible InputFile covers it, so dropping files
   anywhere on the zone and clicking it both reach the same control. */
.media-upload[b-3ucha8883x] {
    position: relative;
    display: grid;
    place-items: center;
    gap: .5rem;
    padding: 1.75rem 1rem;
    border: 2px dashed var(--primary);
    border-radius: 12px;
    background: var(--surface);
}

.media-upload--active[b-3ucha8883x] {
    background: var(--primary-container);
}

.media-upload:focus-within[b-3ucha8883x] {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.media-upload__hint[b-3ucha8883x] {
    margin: 0;
    color: var(--muted);
    font-family: var(--font-ui);
}

.media-upload__input[b-3ucha8883x] {
    position: absolute;
    inset: 0;
    width: 100%;
    height: 100%;
    opacity: 0;
    cursor: pointer;
}

.media-upload__progress[b-3ucha8883x] {
    width: 100%;
    max-width: 320px;
}

.media-grid__tiles[b-3ucha8883x] {
    list-style: none;
    margin: 0;
    padding: 0;
    display: grid;
    grid-template-columns: repeat(auto-fill, minmax(220px, 1fr));
    gap: 1rem;
}

.media-tile[b-3ucha8883x] {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    padding: .75rem;
    border: 1px solid var(--sidebar);
    border-radius: 12px;
    background: var(--surface);
}

.media-tile__pick[b-3ucha8883x] {
    display: flex;
    flex-direction: column;
    gap: .5rem;
    width: 100%;
    padding: 0;
    border: 0;
    background: none;
    cursor: pointer;
    text-align: left;
    font: inherit;
}

.media-tile__pick:focus-visible[b-3ucha8883x] {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

.media-tile__image[b-3ucha8883x] {
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    border-radius: 8px;
    background: var(--primary-container);
}

.media-tile__name[b-3ucha8883x] {
    margin: 0;
    color: var(--muted);
    font-family: var(--font-ui);
    font-size: .875rem;
    overflow-wrap: anywhere;
}

.media-tile__actions[b-3ucha8883x] {
    display: flex;
    flex-wrap: wrap;
    gap: .25rem;
}

.media-tile__confirm[b-3ucha8883x] {
    display: grid;
    gap: .5rem;
    padding-top: .5rem;
    border-top: 1px solid var(--sidebar);
}

.media-tile__confirm-text[b-3ucha8883x] {
    margin: 0;
    color: var(--text);
    font-family: var(--font-ui);
    font-size: .875rem;
}

.media-grid__empty[b-3ucha8883x] {
    margin: 0;
    color: var(--muted);
    font-family: var(--font-ui);
}

.media-grid__error[b-3ucha8883x] {
    margin: 0;
    padding: .75rem 1rem;
    border-left: 4px solid var(--primary);
    background: var(--primary-container);
    color: var(--text);
    font-family: var(--font-ui);
}

/* The Pick-mode scope switch (spec §6.4): "All images" (global, default) or "this post's
   images". Crimson marks the active scope (you are here); the rest stays neutral. */
.media-grid__scopes[b-3ucha8883x] {
    display: flex;
    gap: 6px;
}

.media-grid__scope[b-3ucha8883x] {
    padding: 3px 10px;
    border: 1px solid var(--muted);
    border-radius: 999px;
    background: var(--surface);
    color: var(--text);
    font-family: var(--font-ui);
    font-size: 12.5px;
    cursor: pointer;
}

.media-grid__scope--active[b-3ucha8883x] {
    border-color: var(--primary);
    background: var(--primary-container);
    color: var(--primary);
}

.media-grid__scope:focus-visible[b-3ucha8883x] {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
}
/* /Components/Admin/PostEditorForm.razor.rz.scp.css */
/* The editor form (spec §6.2): the actions bar first (status, saved time, the four actions),
   then the fields. Crimson marks the actions (act); the status badge carries the state colours. */
.post-form[b-tou7bkawe8] {
    display: flex;
    flex-direction: column;
    gap: 14px;
    min-width: 0;
    font-family: var(--font-ui);
}

.post-actions[b-tou7bkawe8] {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
    align-items: center;
    padding: 10px 12px;
    border: 1px solid var(--muted);
    border-radius: 6px;
    background: var(--surface);
}

.post-actions__saved[b-tou7bkawe8] {
    color: var(--muted);
    font-size: 12.5px;
}

.post-actions__buttons[b-tou7bkawe8] {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin-left: auto;
}

.post-actions__button[b-tou7bkawe8] {
    padding: 5px 12px;
    border: 1px solid var(--primary);
    border-radius: 4px;
    background: var(--surface);
    color: var(--primary);
    font-family: inherit;
    font-size: 13px;
    font-weight: 600;
    cursor: pointer;
}

.post-actions__button:hover:enabled[b-tou7bkawe8] {
    background: var(--primary-strong);
    color: var(--surface);
}

.post-actions__button:disabled[b-tou7bkawe8] {
    border-color: var(--muted);
    color: var(--muted);
    cursor: not-allowed;
    opacity: .6;
}

.post-actions__button:focus-visible[b-tou7bkawe8] {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
}

.post-form__field[b-tou7bkawe8] {
    width: 100%;
}

.post-form__tags[b-tou7bkawe8] {
    display: flex;
    gap: 8px;
    align-items: flex-start;
}

.post-form__tag-entry[b-tou7bkawe8] {
    display: flex;
    flex: 1 1 auto;
    flex-direction: column;
    gap: 4px;
    min-width: 0;
}

.post-form__label[b-tou7bkawe8] {
    color: var(--muted);
    font-size: 12.5px;
    font-weight: 600;
}

.post-form__input[b-tou7bkawe8] {
    width: 100%;
    padding: 9px 12px;
    border: 1px solid var(--muted);
    border-radius: 4px;
    background: var(--surface);
    color: var(--text);
    font-family: inherit;
    font-size: 14px;
}

.post-form__input:focus-visible[b-tou7bkawe8] {
    outline: 2px solid var(--primary);
    outline-offset: 1px;
}

.post-form__chips[b-tou7bkawe8] {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.post-form__tag[b-tou7bkawe8] {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    padding: 2px 8px;
    border-radius: 999px;
    background: var(--secondary-container);
    color: var(--on-secondary-container);
    font-size: 12.5px;
    overflow-wrap: anywhere;
}

.post-form__tag-remove[b-tou7bkawe8] {
    border: 0;
    background: none;
    color: inherit;
    font-size: 14px;
    line-height: 1;
    cursor: pointer;
}

.post-form__category[b-tou7bkawe8] {
    display: inline-flex;
    align-items: center;
    gap: 6px;
}

.post-form__when[b-tou7bkawe8] {
    display: flex;
    flex-wrap: wrap;
    gap: 10px;
}

.post-form__when .post-form__field[b-tou7bkawe8] {
    flex: 1 1 180px;
    min-width: 0;
}

.post-form__nav[b-tou7bkawe8] {
    display: flex;
    flex-wrap: wrap;
    gap: 10px 18px;
    align-items: center;
}

.post-form__nav-toggle[b-tou7bkawe8] {
    display: flex;
    align-items: center;
    gap: 6px;
}

.post-form__nav-order[b-tou7bkawe8] {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 1 1 180px;
    min-width: 0;
}
/* /Components/Admin/PostsGrid.razor.rz.scp.css */
/* The admin posts grid (spec §6.1): the admin table uses the UI font and the token colours;
   crimson marks acting links and buttons (colour roles, spec §3.1). The MudDataGrid chrome is a
   child component's markup, so those rules go through ::deep. */
.posts-grid[b-uib52k1ss2] {
    margin-top: 1.25rem;
    font-family: var(--font-ui);
}

.posts-grid[b-uib52k1ss2]  .posts-grid__title {
    color: var(--primary);
    font-weight: 600;
    text-decoration: none;
}

.posts-grid[b-uib52k1ss2]  .posts-grid__title:hover,
.posts-grid[b-uib52k1ss2]  .posts-grid__title:focus-visible {
    text-decoration: underline;
}

.posts-grid[b-uib52k1ss2]  .posts-grid__category {
    display: inline-flex;
    color: var(--muted);
}

.posts-grid[b-uib52k1ss2]  .posts-grid__actions {
    display: flex;
    flex-wrap: wrap;
    gap: 2px 12px;
}

.posts-grid[b-uib52k1ss2]  .posts-grid__action {
    padding: 0;
    border: 0;
    background: none;
    color: var(--primary);
    font: inherit;
    font-size: 13px;
    cursor: pointer;
    text-decoration: none;
}

.posts-grid[b-uib52k1ss2]  .posts-grid__action:hover,
.posts-grid[b-uib52k1ss2]  .posts-grid__action:focus-visible {
    text-decoration: underline;
}

.posts-grid[b-uib52k1ss2]  .posts-grid__empty {
    margin: 1.25rem 0;
    color: var(--muted);
}
/* /Components/Admin/StatusBadge.razor.rz.scp.css */
/* The status badge (spec §3.1): Published uses the green container, Scheduled the amber
   container (light and dark values verbatim from the spec) and Draft neutral grey. */
.status-badge[b-ddr41jvrh3] {
    display: inline-block;
    padding: 1px 7px;
    border-radius: 4px;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .04em;
    line-height: 1.5;
}

.status-badge--published[b-ddr41jvrh3] {
    background: var(--secondary-container);
    color: var(--on-secondary-container);
}

.status-badge--scheduled[b-ddr41jvrh3] {
    background: #FFF1D6;
    color: #7A4B00;
}

.status-badge--draft[b-ddr41jvrh3] {
    background: rgba(128, 128, 128, .25);
    color: var(--muted);
}

/* The Scheduled amber's dark values, via the same dark selectors app.css uses. */
:root[data-theme=dark] .status-badge--scheduled[b-ddr41jvrh3] {
    background: #3A2E14;
    color: #F2C877;
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme=light]) .status-badge--scheduled[b-ddr41jvrh3] {
        background: #3A2E14;
        color: #F2C877;
    }
}

/* The Draft wash needs a deeper ground in dark: --muted on the .25 grey wash over --surface
   falls below AA (4.14:1) with the built-in dark palette, so dark gets a deeper wash
   (4.9:1), light unchanged. */
:root[data-theme=dark] .status-badge--draft[b-ddr41jvrh3] {
    background: rgba(128, 128, 128, .12);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme=light]) .status-badge--draft[b-ddr41jvrh3] {
        background: rgba(128, 128, 128, .12);
    }
}
/* /Components/Admin/ViewsChart.razor.rz.scp.css */
.views-chart[b-uu8o9jxgli] {
    width: 100%;
}

.views-chart__empty[b-uu8o9jxgli] {
    margin: 0;
    color: var(--muted);
    font-family: var(--font-ui);
}
/* /Components/Blog/BannerOrFallback.razor.rz.scp.css */
/* The image area wherever a post may lack a banner: the banner image,
   or the site's fallback banner tiled at 180px under --fallback-wash with the category icon in white. */
.banner-or-fallback[b-v2ymqs9thu] {
    position: relative;
    display: block;
    width: 100%;
    height: 100%;
    overflow: hidden;
}

.banner-or-fallback__img[b-v2ymqs9thu] {
    display: block;
    width: 100%;
    height: 100%;
    object-fit: cover;
}

/* The fallback: the site's fallback-banner image (theme.css's --fallback-banner token, with its
   content-hash cache-buster) repeats at 180px under --fallback-wash. The wash is its own
   layer — the background shorthand only allows a colour in its LAST layer, so
   "background: var(--fallback-wash), url(...)" would be invalid and dropped. */
.banner-or-fallback__fallback[b-v2ymqs9thu] {
    position: absolute;
    inset: 0;
    background-image: var(--fallback-banner);
    background-position: 0 0;
    background-size: 180px;
    background-repeat: repeat;
}

.banner-or-fallback__fallback[b-v2ymqs9thu]::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--fallback-wash);
}

.banner-or-fallback__icon[b-v2ymqs9thu] {
    position: absolute;
    inset: 0;
    z-index: 1;
    display: flex;
    align-items: center;
    justify-content: center;
    color: #FFFFFF;
    filter: drop-shadow(0 2px 8px rgba(0, 0, 0, .45));
}

/* With text overlaid at the bottom the icon moves to the top-right. */
.banner-or-fallback--overlay .banner-or-fallback__icon[b-v2ymqs9thu] {
    inset: 14px 18px auto auto;
}

.banner-or-fallback--overlay[b-v2ymqs9thu]::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(0, 0, 0, .75), rgba(0, 0, 0, 0) 65%);
}
/* /Components/Blog/CategoryChip.razor.rz.scp.css */
/* The green category chip (green classifies, crimson acts).
   Long names ellipsize; the full name stays in title and text. */
.category-chip[b-mmjmrhc4sj] {
    display: inline-flex;
    align-items: center;
    gap: 4px;
    max-width: 100%;
    padding: 2px 10px;
    border-radius: 10px;
    background: var(--secondary-container);
    color: var(--on-secondary-container);
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.5;
    text-decoration: none;
    white-space: nowrap;
}

.category-chip:hover .category-chip__name[b-mmjmrhc4sj] {
    text-decoration: underline;
}

.category-chip__name[b-mmjmrhc4sj] {
    overflow: hidden;
    text-overflow: ellipsis;
}
/* /Components/Blog/ContentsPanel.razor.rz.scp.css */
/* The rail-less "On this page" panel, in its three states. The state classes are toggled by
   wwwroot/js/post.js, which also keeps the panel's flow footprint constant while it shrinks to
   the row or opens as a drawer: it writes height and margin-bottom through the CSSOM so
   height + margin always add up to the block's natural height and nothing below the panel ever
   jumps. The row height (48px) is a touch target and the pages' heading scroll-margin-top
   (Post.razor.css, DraftReviewPage.razor.css) — change all three together. */

/* At content widths where the rail shows (main's 1000px @container threshold) the rail carries
   Tags + "On this page" and this panel renders nothing — on either post surface: neither the
   panel nor its scrim. */
@container (min-width: 1000px) {
    .toc-inline[b-o3fwpvs1wm],
    .toc-inline__scrim[b-o3fwpvs1wm] {
        display: none;
    }
}

.toc-inline[b-o3fwpvs1wm] {
    min-width: 0;
    /* The block's air above the hero (brief 07i item 1): the same margin on the same structural
       edge — top of the block that follows the hero — as the text gets on a post without a panel
       (.post-layout's margin-top in Post.razor.css and DraftReviewPage.razor.css). The hero then
       leaves the panel exactly the space it leaves the text, and the text's own margin-top
       supplies the same again below the panel. Change the three values together. */
    margin-top: 2.5rem;
    font-family: var(--font-ui);
    font-size: 13px;
    color: var(--muted);
    transition: height 200ms ease, margin-bottom 200ms ease;
}

/* Pinning is part of the enhancement: without JavaScript (and before the module runs) the block
   stays in normal flow — nothing floats over the text while it scrolls. */
.toc-inline--enhanced[b-o3fwpvs1wm] {
    position: sticky;
    top: 0;
    z-index: 30;
}

/* States 2 and 3 sit over the scrolling text. */
.toc-inline--pinned[b-o3fwpvs1wm],
.toc-inline--open[b-o3fwpvs1wm] {
    background: var(--bg);
}

.toc-inline--pinned[b-o3fwpvs1wm] {
    overflow: hidden;
    border-bottom: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
}

/* State 3 (the drawer) floats over the scrim-dimmed page: it takes an elevation shadow. The
   theme defines no elevation token (the brandable palette is colour-only), so the drawer's
   shadow is the specified one (brief 07i item 3): 0 4px 16px black 24%. */
.toc-inline--open[b-o3fwpvs1wm] {
    box-shadow: 0 4px 16px rgb(0 0 0 / 24%);
}

/* The one-row bar: the current section's title and a chevron that doubles as the pull handle. */
.toc-inline__row[b-o3fwpvs1wm] {
    display: none;
    width: 100%;
    height: 48px;
    align-items: center;
    gap: 8px;
    padding: 0 4px 0 12px;
    border: 0;
    background: none;
    color: var(--text);
    font-family: var(--font-ui);
    font-size: 14px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}

.toc-inline--pinned .toc-inline__row[b-o3fwpvs1wm],
.toc-inline--open .toc-inline__row[b-o3fwpvs1wm] {
    display: flex;
}

.toc-inline__row-text[b-o3fwpvs1wm] {
    flex: 1 1 auto;
    min-width: 0;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The pull target: its own touch-action so a drag pulls the drawer instead of scrolling the page. */
.toc-inline__handle[b-o3fwpvs1wm] {
    flex: 0 0 auto;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 44px;
    height: 48px;
    color: var(--muted);
    touch-action: none;
    cursor: grab;
}

.toc-inline__chevron[b-o3fwpvs1wm] {
    transition: transform 200ms ease;
}

.toc-inline--open .toc-inline__chevron[b-o3fwpvs1wm] {
    transform: rotate(180deg);
}

/* State 1 (the block): the list on the left, the tags in a column on the right. */
.toc-inline__body[b-o3fwpvs1wm] {
    display: flex;
    align-items: flex-start;
    gap: 16px;
}

.toc-inline__list-column[b-o3fwpvs1wm] {
    flex: 1 1 auto;
    min-width: 0;
}

.toc-inline__tags[b-o3fwpvs1wm] {
    flex: 0 1 auto;
    max-width: 42%;
}

/* Closed and pinned the block collapses away behind the row. */
.toc-inline--pinned:not(.toc-inline--open) .toc-inline__body[b-o3fwpvs1wm] {
    display: none;
}

/* State 3 (the drawer): the full list, scrolling inside at most 70vh, with the row as its header. */
.toc-inline--open .toc-inline__row[b-o3fwpvs1wm] {
    padding-right: 8px;
}

.toc-inline--open[b-o3fwpvs1wm]  .toc__title {
    display: none;
}

.toc-inline--open[b-o3fwpvs1wm]  .toc__list {
    max-height: 70vh;
    overflow-y: auto;
}

/* State 1 shows at most five entries — and only once the states are enhanced in at all
   (post.js adds --enhanced): without JavaScript nothing is truncated and the "+N more…"
   button, whose drawer needs JavaScript, is not shown. The truncation and its button are one
   promise: either both (over five sections) or neither. */
.toc-inline--enhanced:not(.toc-inline--open)[b-o3fwpvs1wm]  .toc__list > li:nth-child(n + 6) {
    display: none;
}

.toc-inline__more[b-o3fwpvs1wm] {
    display: none;
    margin: 2px 0 0;
    padding: 6px 0;
    border: 0;
    background: none;
    color: var(--primary);
    font-family: var(--font-ui);
    font-size: 13px;
    font-weight: 600;
    text-align: left;
    cursor: pointer;
}

.toc-inline--enhanced .toc-inline__more[b-o3fwpvs1wm] {
    display: block;
}

.toc-inline--enhanced.toc-inline--open .toc-inline__more[b-o3fwpvs1wm] {
    display: none;
}

/* The drawer's bottom strip (brief 07i item 2): the pull-up target, at least 24px tall, with
   the grab bar centred in it — 36×4, fully rounded, the muted text colour. Shown only while the
   drawer is open; the closed block and the pinned row show nothing. */
.toc-inline__strip[b-o3fwpvs1wm] {
    display: none;
    height: 28px;
    align-items: center;
    justify-content: center;
    margin-top: 12px;
    touch-action: none;
    cursor: grab;
}

.toc-inline--open .toc-inline__strip[b-o3fwpvs1wm] {
    display: flex;
}

.toc-inline__grab[b-o3fwpvs1wm] {
    width: 36px;
    height: 4px;
    border-radius: 999px;
    background: var(--muted);
}

/* The scrim over the page below the opened drawer (brief 07i item 3). The panel's stacking
   context (z-index 30) sits above it, so the drawer paints over the dim while everything
   below the drawer's bottom edge shows it. Its top edge follows the drawer through the CSSOM
   (post.js); closed it is invisible and outside the hit test, so it can live in the DOM for
   both states and fade between them. The alpha itself never appears here: the theme's --scrim
   token (app.css) carries it, so the public pages' style-attribute-free policy holds. */
.toc-inline__scrim[b-o3fwpvs1wm] {
    position: fixed;
    left: 0;
    right: 0;
    bottom: 0;
    z-index: 20;
    background: var(--scrim);
    opacity: 0;
    visibility: hidden;
    transition: opacity 200ms ease, visibility 200ms ease;
}

.toc-inline--open ~ .toc-inline__scrim[b-o3fwpvs1wm] {
    opacity: 1;
    visibility: visible;
}

@media (prefers-reduced-motion: reduce) {
    .toc-inline[b-o3fwpvs1wm],
    .toc-inline__chevron[b-o3fwpvs1wm],
    .toc-inline__scrim[b-o3fwpvs1wm] {
        transition: none;
    }
}
/* /Components/Blog/PageHeader.razor.rz.scp.css */
/* The list-page header (spec §5.3): serif name, muted "· N posts" count. */
.page-header[b-yegkui6ujq] {
    margin: 0 0 1.25rem;
}

.page-header__title[b-yegkui6ujq] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 10px;
    margin: 0;
    font-family: var(--font-serif);
    font-size: 30px;
    line-height: 1.2;
    color: var(--text);
}

.page-header__meta[b-yegkui6ujq] {
    font-family: var(--font-ui);
    font-size: 16px;
    font-weight: 400;
    color: var(--muted);
}
/* /Components/Blog/PagePager.razor.rz.scp.css */
/* The link-based pager, styled as a pager: crimson "you are here"
   on the current page (spec §4.2's active styling). */
.page-pager[b-tbcg98mqkg] {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 8px 0 0;
}

.page-pager__link[b-tbcg98mqkg] {
    min-width: 2.25rem;
    padding: 4px 10px;
    border: 1px solid transparent;
    border-radius: 8px;
    background: var(--surface);
    color: var(--primary);
    font-family: var(--font-ui);
    font-size: 14px;
    text-align: center;
    text-decoration: none;
}

.page-pager__link:hover[b-tbcg98mqkg] {
    background: var(--primary-container);
}

.page-pager__link--current[b-tbcg98mqkg] {
    background: var(--primary-strong);
    color: #FFFFFF;
    font-weight: 600;
}
/* /Components/Blog/PageSurface.razor.rz.scp.css */
/* The page surface's reading column (brief 07g item 3): the same geometry block as the post
   page's text column (Post.razor.css), rail track included — the post page centres a
   text-plus-rail grid where the content area is at least 1000px wide, and this surface keeps the
   rail's 13.75rem track empty so its body column lands on exactly the same left/right edges as a
   post's (the E2E suite compares them ±1px). The measure is declared in the READING font so its
   "ch" is the body text's "0" width, exactly like Post.razor.css. Change both files together.

   .page-surface__layout { display: grid; grid-template-columns: minmax(0, <measure>); gap: 3rem; }
   @container (min-width: 1000px) {
       .page-surface__layout { grid-template-columns: minmax(0, <measure>) 13.75rem; justify-content: center; }
   }

   <measure> is clamp(68ch, calc(68ch + (100vw - 1440px) * 0.42), 88ch) in Source Serif 4 at the
   body text's size (1.125rem, 1.0625rem below 960px). The container is main (.shell > main,
   container-type: inline-size), so the query measures the content area. */
.page-surface__layout[b-g8cy9pjjkx] {
    display: grid;
    grid-template-columns: minmax(0, clamp(68ch, calc(68ch + (100vw - 1440px) * 0.42), 88ch));
    gap: 3rem;
    font-family: var(--font-serif);
    font-size: 1.125rem;
    margin-top: 2.5rem;
    padding-bottom: 4rem;
}

/* The measure must track the body text's size, which app.css drops to 1.0625rem on phones. */
@media (max-width: 959.98px) {
    .page-surface__layout[b-g8cy9pjjkx] {
        font-size: 1.0625rem;
    }
}

@container (min-width: 1000px) {
    .page-surface__layout[b-g8cy9pjjkx] {
        grid-template-columns: minmax(0, clamp(68ch, calc(68ch + (100vw - 1440px) * 0.42), 88ch)) 13.75rem;
        justify-content: center;
    }
}

/* The plain title header above the body, in the same column. */
.page-surface__header[b-g8cy9pjjkx] {
    margin-bottom: 2.5rem;
}

.page-surface__title[b-g8cy9pjjkx] {
    margin: 0 0 .5rem;
}

.page-surface__subtitle[b-g8cy9pjjkx] {
    margin: 0 0 .75rem;
    font-size: 1.25rem;
}
/* /Components/Blog/PostBody.razor.rz.scp.css */
/* Spec §5.4 content styling: images scale to the column (rounded, captioned from the Markdown
   title), code blocks use --code-bg with syntax highlighting, tables get light borders. The
   rendered markdown is raw HTML (MarkupString), which carries no scope attribute, so every rule
   reaching inside .markdown-body goes through ::deep — otherwise nothing here would match. */

/* One vertical rhythm, in em so the gaps follow the body text's size (1.125rem, 1.0625rem below
   960px): a paragraph keeps one em of air below it, blocks step by the same measure, and headings
   breathe more above than below. The global reset zeroes margins and nothing here set them back,
   so separate paragraphs used to run together. The body's edges stay flush: its first child has no
   top margin, its last child no bottom margin — the rhythm is only between elements. */
.markdown-body[b-1m46aiywit]  p {
    margin: 0 0 1em;
}

.markdown-body[b-1m46aiywit]  h2 {
    margin: 1.6em 0 .5em;
}

.markdown-body[b-1m46aiywit]  h3 {
    margin: 1.3em 0 .4em;
}

.markdown-body[b-1m46aiywit]  h4,
.markdown-body[b-1m46aiywit]  h5,
.markdown-body[b-1m46aiywit]  h6 {
    margin: 1.1em 0 .3em;
}

.markdown-body[b-1m46aiywit]  > :first-child {
    margin-top: 0;
}

.markdown-body[b-1m46aiywit]  > :last-child {
    margin-bottom: 0;
}

.markdown-body[b-1m46aiywit]  img {
    display: block;
    max-width: 100%;
    height: auto;
    margin: 1.25rem auto;
    border-radius: 8px;
}

.markdown-body[b-1m46aiywit]  figure {
    margin: 0 0 1em;
}

.markdown-body[b-1m46aiywit]  figcaption {
    margin-top: .45rem;
    color: var(--muted);
    font-family: var(--font-ui);
    font-size: 0.8125rem;
    text-align: center;
}

.markdown-body[b-1m46aiywit]  pre {
    margin: 0 0 1em;
    padding: 14px 16px;
    overflow-x: auto;
    border-radius: 8px;
    background: var(--code-bg);
    color: var(--code-text);
    font-size: 0.875rem;
    line-height: 1.55;
}

.markdown-body[b-1m46aiywit]  pre code {
    padding: 0;
    background: none;
}

/* Axe link-in-text-block: MudBlazor's global a{text-decoration:none}
   reset strips the underline, so inline links in running text would rely on colour alone — and
   --primary against --text is only 1.44:1 (light) / 1.98:1 (dark). Content links are underlined;
   component links (cards, nav, chips) keep their own designs. */
.markdown-body[b-1m46aiywit]  a {
    text-decoration: underline;
}

.markdown-body[b-1m46aiywit]  :not(pre) > code {
    padding: 1px 5px;
    border-radius: 4px;
    background: var(--primary-container);
    color: var(--text);
    font-size: .85em;
}

.markdown-body[b-1m46aiywit]  table {
    width: 100%;
    margin: 0 0 1em;
    border-collapse: collapse;
}

.markdown-body[b-1m46aiywit]  th,
.markdown-body[b-1m46aiywit]  td {
    padding: 6px 10px;
    border: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
    text-align: left;
}

.markdown-body[b-1m46aiywit]  th {
    background: var(--primary-container);
}

.markdown-body[b-1m46aiywit]  blockquote {
    margin: 0 0 1em;
    padding: .25rem 1rem;
    border-left: 3px solid var(--primary-container);
    color: var(--muted);
}

/* The global reset zeroes list padding and nothing set it back, so outside markers rendered left
   of the list box — left of the text column on desktop and across the screen edge on phones. The
   padding strip is where outside markers render: 1.5em keeps them right of the paragraph edge
   with the item text one step further in, and a nested list repeats the step from its parent's
   content edge. Lists follow the same rhythm as the rest of the body: one em below the whole
   list, its items stacked like lines of text with no extra space between them, and a nested list
   flush inside its item. */
.markdown-body[b-1m46aiywit]  ul,
.markdown-body[b-1m46aiywit]  ol {
    margin: 0 0 1em;
    padding-left: 1.5em;
}

.markdown-body[b-1m46aiywit]  li + li {
    margin-top: 0;
}

.markdown-body[b-1m46aiywit]  li > ul,
.markdown-body[b-1m46aiywit]  li > ol {
    margin: 0;
}
/* /Components/Blog/PostCard.razor.rz.scp.css */
/* PostCard (spec §5.1). Featured: image area aspect-ratio 21/9 with
   max-height 420px (phones 4/3), chip/title/subtitle overlaid on the gradient. List: image left
   at 34% (min 96px), text right. Summaries clamp with -webkit-line-clamp: Featured 3 lines,
   List 2. Long titles/subtitles clamp too. */
.post-card[b-lt8bydhzm8] {
    position: relative;
    background: var(--surface);
    border-radius: 10px;
    overflow: hidden;
    box-shadow: 0 1px 3px rgba(60, 20, 30, .15);
}

.post-card__link[b-lt8bydhzm8] {
    display: block;
    color: inherit;
    text-decoration: none;
}

.post-card__link:focus-visible[b-lt8bydhzm8] {
    outline: 3px solid var(--primary);
    outline-offset: -3px;
}

.post-card__media[b-lt8bydhzm8] {
    position: relative;
}

.post-card--featured .post-card__media[b-lt8bydhzm8] {
    aspect-ratio: 21 / 9;
    max-height: 420px;
}

@media (max-width: 959.98px) {
    .post-card--featured .post-card__media[b-lt8bydhzm8] {
        aspect-ratio: 4 / 3;
    }
}

.post-card__overlay[b-lt8bydhzm8] {
    position: absolute;
    left: 16px;
    right: 16px;
    bottom: 12px;
    z-index: 2;
    min-width: 0;
}

/* List: image left at 34% (min 96px). The chip's left offset shares --list-img-w with the media's
   flex basis so the chip row above the text lines up with the text column at any width. */
.post-card--list[b-lt8bydhzm8] {
    --list-img-w: max(34%, 96px);
}

.post-card--list .post-card__link[b-lt8bydhzm8] {
    display: flex;
    min-width: 0;
}

.post-card--list .post-card__media[b-lt8bydhzm8] {
    flex: 0 0 var(--list-img-w);
    min-height: 96px;
}

/* The list media is a definite box the image fills absolutely (the fallback banner already behaves
   this way): the <img> contributes no intrinsic height, so the row stays text-sized and
   object-fit: cover crops the banner to the box (spec §11: images are served as uploaded, the CSS
   crops). Without this the img's intrinsic ratio drives the row height (a square banner rendered
   382px-tall rows). */
.post-card--list .post-card__media[b-lt8bydhzm8]  .banner-or-fallback {
    position: absolute;
    inset: 0;
    width: auto;
    height: auto;
}

.post-card--list .post-card__text[b-lt8bydhzm8] {
    flex: 1;
}

.post-card__text[b-lt8bydhzm8] {
    padding: 12px 16px 14px;
    min-width: 0;
}

.post-card--list .post-card__text[b-lt8bydhzm8] {
    padding-top: 2.6rem;
}

.post-card--list.post-card--no-chip .post-card__text[b-lt8bydhzm8] {
    padding-top: 12px;
}

.post-card__title[b-lt8bydhzm8] {
    margin: 0;
    overflow-wrap: break-word;
    font-family: var(--font-serif);
    font-weight: 700;
    line-height: 1.25;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.post-card--featured .post-card__title[b-lt8bydhzm8] {
    color: #FFFFFF;
    font-size: 1.5rem;
}

.post-card--list .post-card__title[b-lt8bydhzm8] {
    color: var(--text);
    font-size: 1.0625rem;
}

.post-card__subtitle[b-lt8bydhzm8] {
    margin: 4px 0 0;
    overflow-wrap: break-word;
    font-size: 0.875rem;
    line-height: 1.35;
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.post-card--featured .post-card__overlay .post-card__subtitle[b-lt8bydhzm8] {
    color: rgba(255, 255, 255, .92);
}

.post-card__summary[b-lt8bydhzm8] {
    margin: 10px 0 0;
    overflow-wrap: break-word;
    color: var(--text);
    font-family: var(--font-serif);
    font-size: 0.9375rem;
    line-height: 1.5;
    display: -webkit-box;
    -webkit-box-orient: vertical;
    overflow: hidden;
}

.post-card--featured .post-card__summary[b-lt8bydhzm8] {
    -webkit-line-clamp: 3;
}

.post-card--list .post-card__summary[b-lt8bydhzm8] {
    -webkit-line-clamp: 2;
}

.post-card__footer[b-lt8bydhzm8] {
    display: flex;
    align-items: center;
    flex-wrap: wrap;
    gap: 12px;
    margin-top: 12px;
}

.post-card__more[b-lt8bydhzm8] {
    color: var(--primary);
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    white-space: nowrap;
}

/* The chip link sits over the card link (an <a> cannot contain another <a>). */
.post-card__chip[b-lt8bydhzm8] {
    position: absolute;
    top: 12px;
    left: 12px;
    z-index: 3;
    max-width: calc(100% - 24px);
}

.post-card--list .post-card__chip[b-lt8bydhzm8] {
    top: 10px;
    left: calc(var(--list-img-w) + 16px);
    max-width: calc(100% - var(--list-img-w) - 32px);
}
/* /Components/Blog/PostHero.razor.rz.scp.css */
/* The post hero (spec §5.4): at least 360px tall (240px on phones), with the text overlaid on the
   banner under the hero's own dark overlay (the text-anchored gradient below). The height is a
   minimum, not a fixed box: the content stays in flow (bottom-anchored through the hero's flex-end
   and padding) so a long title grows the hero instead of running off its top or over the banner's
   edge, and the title and subtitle wrap long unbroken words rather than overflowing.
   box-sizing: border-box keeps the minimum the old fixed height including the padding. The title
   takes app.css's .hero-title. The padding values are custom properties because the overlay's
   pseudo-element must extend past the content box to the hero's bottom and sides. */
.post-hero[b-fobnqlg9pv] {
    --post-hero-pad-x: 28px;
    --post-hero-pad-b: 18px;
    position: relative;
    display: flex;
    flex-direction: column;
    justify-content: flex-end;
    box-sizing: border-box;
    min-height: 360px;
    padding: 28px var(--post-hero-pad-x) var(--post-hero-pad-b);
    overflow: hidden;
}

.post-hero__media[b-fobnqlg9pv] {
    position: absolute;
    inset: 0;
}

/* In flow (was absolutely positioned over the media) so the hero's height follows the text;
   the hero's padding replaces the old right/bottom/left insets one for one. */
.post-hero__content[b-fobnqlg9pv] {
    position: relative;
    z-index: 1;
    display: flex;
    flex-direction: column;
    align-items: flex-start;
    gap: 6px;
    min-width: 0;
}

/* The hero's dark overlay follows its text block instead of the hero's height: a long title grows
   the hero, so a fixed-height gradient band (BannerOrFallback's, 65% of the hero) leaves its top
   lines on the bare banner. The gradient is anchored to the content box — the text block's top
   edge is exactly 4rem below this pseudo-element's top: alpha .75 at the hero's bottom (what the
   band always gave), .55 at the text block's top and everything between (white text needs 4.5:1,
   i.e. a backdrop of sRGB <= 118 per channel; 255 × (1 − .55) ≈ 115), fading linearly to 0 over
   the 4rem above the text block. The pseudo-element extends past the content box to the hero's
   bottom and sides (the hero's padding, one for one); z-index -1 paints it behind the text but,
   inside .post-hero__content's stacking context, above the media. */
.post-hero__content[b-fobnqlg9pv]::before {
    content: "";
    position: absolute;
    top: -4rem;
    left: calc(-1 * var(--post-hero-pad-x));
    right: calc(-1 * var(--post-hero-pad-x));
    bottom: calc(-1 * var(--post-hero-pad-b));
    z-index: -1;
    background: linear-gradient(to bottom, rgba(0, 0, 0, 0) 0, rgba(0, 0, 0, .55) 4rem, rgba(0, 0, 0, .75) 100%);
    pointer-events: none;
}

/* The hero keeps its category icon in the top-right: its text still sits at the bottom, the
   reason BannerOrFallback's --overlay modifier moves the icon there — the hero just draws the
   gradient itself now. */
.post-hero__media[b-fobnqlg9pv]  .banner-or-fallback__icon {
    inset: 14px 18px auto auto;
}

.post-hero__title[b-fobnqlg9pv] {
    margin: 0;
    color: #FFFFFF;
    overflow-wrap: anywhere;
}

.post-hero__subtitle[b-fobnqlg9pv] {
    margin: 0;
    color: rgba(255, 255, 255, .85);
    overflow-wrap: anywhere;
}

/* The meta line sits on the dark gradient — override PostMeta's muted colour through ::deep
   (scoped CSS cannot reach a child component's root element). */
.post-hero__content[b-fobnqlg9pv]  .post-meta {
    color: rgba(255, 255, 255, .85);
}

.post-hero__content[b-fobnqlg9pv]  .post-meta__status {
    background: rgba(255, 255, 255, .92);
}

/* The Draft ribbon sits above the category chip on preview/draft surfaces.
   --primary, not --primary-hover: spec §3.1 makes --primary the text accent. */
.post-hero__ribbon[b-fobnqlg9pv] {
    padding: 2px 7px;
    border-radius: 4px;
    background: var(--primary-container);
    color: var(--primary);
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .06em;
    text-transform: uppercase;
}

@media (max-width: 959.98px) {
    .post-hero[b-fobnqlg9pv] {
        --post-hero-pad-x: 18px;
        --post-hero-pad-b: 14px;
        min-height: 240px;
        padding: 18px var(--post-hero-pad-x) var(--post-hero-pad-b);
    }
}
/* /Components/Blog/PostList.razor.rz.scp.css */
/* The stacked card list. */
.post-list[b-wxzst7syjz] {
    display: flex;
    flex-direction: column;
    gap: 14px;
}

.post-list__empty[b-wxzst7syjz] {
    margin: 0;
    color: var(--muted);
    font-family: var(--font-ui);
    font-size: 15px;
}
/* /Components/Blog/PostMeta.razor.rz.scp.css */
/* "2 October 2026 · 7 min read" (spec §5.1) and the optional status badge — the badge itself is
   the shared StatusBadge component (spec §7.4), styled in its own css. */
.post-meta[b-va0j2yut12] {
    display: inline-flex;
    align-items: center;
    gap: 8px;
    min-width: 0;
    color: var(--muted);
    font-family: var(--font-ui);
    font-size: 12.5px;
    line-height: 1.5;
}

.post-meta__when[b-va0j2yut12] {
    overflow-wrap: anywhere;
}
/* /Components/Blog/PostRail.razor.rz.scp.css */
/* The right-hand column: stacked panels with small uppercase
   headings. The page's @container rules (Post.razor.css) turn this element into display: contents
   below 1000px of content width. */
.post-rail[b-yy9zc8w67f] {
    display: flex;
    flex-direction: column;
    gap: 1.75rem;
    min-width: 0;
    font-family: var(--font-ui);
    /* The page's layout grid is declared in the reading font at the body text's size (so its
       ch means body characters — Post.razor.css), which would otherwise grow everything under
       it. The rail keeps the shell body size it has always inherited (MudBlazor's 0.875rem
       baseline on body) and the UI font. */
    font-size: 0.875rem;
}

.post-rail__panel[b-yy9zc8w67f] {
    min-width: 0;
}

.post-rail__heading[b-yy9zc8w67f] {
    margin: 0 0 8px;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text);
}
/* /Components/Blog/RelatedPosts.razor.rz.scp.css */
/* Related posts after the post body (brief 07d item 3): up to 3 cards — a 16:9 thumbnail above
   the title — in the text column. The root is the container for the card grid: three equal
   columns with a 1.5rem gap at ≥600px of section width (the container query measures this
   element's own width — the text column's), one column below. The heading keeps the rail's
   panel-heading look (PostRail.razor.css's .post-rail__heading). */
.related-posts[b-7c3j4scqwh] {
    container-type: inline-size;
    min-width: 0;
    font-family: var(--font-ui);
}

.related-posts__heading[b-7c3j4scqwh] {
    margin: 0 0 8px;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text);
}

.related-posts__list[b-7c3j4scqwh] {
    display: grid;
    grid-template-columns: 1fr;
    gap: 1.5rem;
    margin: 0;
    padding: 0;
    list-style: none;
}

@container (min-width: 600px) {
    .related-posts__list[b-7c3j4scqwh] {
        grid-template-columns: repeat(3, 1fr);
    }
}

.related-posts__item[b-7c3j4scqwh] {
    min-width: 0;
}

.related-posts__link[b-7c3j4scqwh] {
    display: flex;
    flex-direction: column;
    gap: 8px;
    min-width: 0;
    color: var(--text);
    text-decoration: none;
}

/* The thumbnail is full card width at 16:9 with the shared 6px radius (the cards' image area). */
.related-posts__thumb[b-7c3j4scqwh] {
    display: block;
    width: 100%;
    aspect-ratio: 16 / 9;
    overflow: hidden;
    border-radius: 6px;
}

/* The title is the UI font at 15px, clamped to two lines with an ellipsis. */
.related-posts__title[b-7c3j4scqwh] {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    min-width: 0;
    font-family: var(--font-ui);
    font-size: 15px;
    line-height: 1.35;
}

.related-posts__link:hover .related-posts__title[b-7c3j4scqwh] {
    color: var(--primary);
    text-decoration: underline;
}
/* /Components/Blog/TableOfContents.razor.rz.scp.css */
/* The "On this page" list: quiet links with a left rule;
   the current section (scroll-spy) is marked in --primary and heavier weight (spec §3.1: --primary
   is the "links, current page" accent; --primary-hover is only the filled-primary hover/pressed
   state and its dark value fails contrast as text). */
.toc[b-wtord3cr1u] {
    min-width: 0;
    font-family: var(--font-ui);
    font-size: 13px;
    color: var(--muted);
}

.toc__title[b-wtord3cr1u] {
    margin: 0 0 8px;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--text);
}

.toc__list[b-wtord3cr1u] {
    margin: 0;
    padding: 0;
    list-style: none;
}

.toc__link[b-wtord3cr1u] {
    display: block;
    padding: 3px 0 3px 10px;
    border-left: 2px solid var(--primary-container);
    color: var(--muted);
    text-decoration: none;
    overflow-wrap: anywhere;
}

.toc__link--3[b-wtord3cr1u] {
    padding-left: 22px;
}

.toc__link:hover[b-wtord3cr1u] {
    color: var(--text);
}

.toc__link--active[b-wtord3cr1u] {
    border-left-color: var(--primary);
    color: var(--primary);
    font-weight: 600;
}
/* /Components/Blog/TagPills.razor.rz.scp.css */
/* Green tag pills (green classifies, crimson acts). The row wraps so 20 tags never overflow. */
.tag-pills[b-jfle6v5qpg] {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
    margin: 0;
    padding: 0;
    list-style: none;
}

.tag-pill[b-jfle6v5qpg] {
    display: inline-block;
    padding: 2px 10px;
    border: 1px solid var(--secondary);
    border-radius: 10px;
    color: var(--on-secondary-container);
    font-family: var(--font-ui);
    font-size: 12px;
    font-weight: 600;
    line-height: 1.5;
    text-decoration: none;
    overflow-wrap: anywhere;
}

.tag-pill:hover[b-jfle6v5qpg] {
    background: var(--secondary-container);
}
/* /Components/Layout/BrandBlock.razor.rz.scp.css */
.brand[b-c5910xx9fp] {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    padding: 6px;
    border-radius: 10px;
    color: inherit;
    text-decoration: none;
}

.brand:hover[b-c5910xx9fp] {
    background: var(--primary-container);
}

.brand-logo[b-c5910xx9fp] {
    width: 32px;
    height: 32px;
    flex-shrink: 0;
    border-radius: 7px;
}

.brand-copy[b-c5910xx9fp] {
    display: flex;
    flex-direction: column;
    min-width: 0;
}

.brand-name[b-c5910xx9fp] {
    font-family: var(--font-serif);
    font-size: 17px;
    font-weight: 700;
    line-height: 1.25;
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.brand-tagline[b-c5910xx9fp] {
    font-family: var(--font-serif);
    font-style: italic;
    font-size: 12.5px;
    color: var(--muted);
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.compact .brand-logo[b-c5910xx9fp] {
    width: 26px;
    height: 26px;
}

/* 960-1279px rail: just the logo, and in the drawer the name and tagline beside it. The logo is
   pinned left (8px insets its 32px box over the 20px icons' centred column, centre on centre)
   instead of centred: centring the row would slide it left by half the copy's width in the rail
   and right by half of it in the drawer. */
@media (min-width: 960px) and (max-width: 1279.98px) {
    .brand[b-c5910xx9fp] {
        justify-content: flex-start;
        padding: 6px 0 6px 8px;
    }
}
/* /Components/Layout/MainLayout.razor.rz.scp.css */
/* The shell grid itself lives in wwwroot/app.css (it styles <html> classes); this file only
   pads the content area and styles the Blazor error UI. Keep the left padding — the theme
   tests sample page background just inside main's left edge. */
.shell > main[b-28ywbbsyjl] {
    padding: 24px 28px 48px;
}

/* The content wrapper caps every page at 80rem and centres it within main (brief 03f item 2).
   It must stay inside main and keep position: static — main is the post/draft-review rail's
   @container (container-type: inline-size in app.css) and the "Add feedback" button's containing
   block, and neither may key on the wrapper. */
.shell > main > .content[b-28ywbbsyjl] {
    max-width: 80rem;
    margin-inline: auto;
}

@media (max-width: 959.98px) {
    .shell > main[b-28ywbbsyjl] {
        padding: 16px 16px 40px;
    }
}

#blazor-error-ui[b-28ywbbsyjl] {
    color-scheme: light only;
    background: lightyellow;
    bottom: 0;
    box-shadow: 0 -1px 2px rgba(0, 0, 0, 0.2);
    box-sizing: border-box;
    display: none;
    left: 0;
    padding: 0.6rem 1.25rem 0.7rem 1.25rem;
    position: fixed;
    width: 100%;
    z-index: 1000;
}

    #blazor-error-ui .dismiss[b-28ywbbsyjl] {
        cursor: pointer;
        position: absolute;
        right: 0.75rem;
        top: 0.5rem;
    }
/* /Components/Layout/NavItem.razor.rz.scp.css */
/* Scoped CSS cannot reach NavLink's root element (the scope attribute lands on
   elements rendered by this component, not on the child component's root), so before the
   .nav-item-wrap wrapper + ::deep these rules never matched the link. Everything after ::deep is
   unscoped — the styles land on the <a> itself, which LayoutTests measures via computed styles. */
.nav-item-wrap[b-nj4gzm4r1e]  .nav-item {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    padding: 8px 10px;
    border-radius: 8px;
    color: var(--sidebar-text);
    text-decoration: none;
    font-family: var(--font-ui);
    font-size: 14.5px;
    line-height: 1.3;
}

.nav-item-wrap[b-nj4gzm4r1e]  .nav-item:hover {
    background: var(--primary-container);
    color: var(--text);
}

/* The current page is highlighted with --primary-strong and white text (spec §4.2). */
.nav-item-wrap[b-nj4gzm4r1e]  .nav-item.active {
    background: var(--primary-strong);
    color: #FFFFFF;
}

.nav-item-wrap[b-nj4gzm4r1e]  .nav-item .label {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

.nav-item-wrap[b-nj4gzm4r1e]  .nav-item .count {
    margin-left: auto;
    font-size: 12px;
    color: var(--muted);
}

.nav-item-wrap[b-nj4gzm4r1e]  .nav-item.active .count {
    color: #FFFFFF;
}

/* 960-1279px rail: centred icons; labels are hidden globally, restored when the overlay opens.
   The drawer's left padding (14px) is the rail's centred icon slot ((48 − 20) / 2), so every icon
   stays exactly where the rail shows it and only the labels appear beside it. */
@media (min-width: 960px) and (max-width: 1279.98px) {
    .nav-item-wrap[b-nj4gzm4r1e]  .nav-item {
        justify-content: center;
        padding: 8px 0;
    }

    html.nav-open .nav-item-wrap[b-nj4gzm4r1e]  .nav-item {
        justify-content: flex-start;
        padding: 8px 10px 8px 14px;
    }
}
/* /Components/Layout/ReconnectModal.razor.rz.scp.css */
.components-reconnect-first-attempt-visible[b-qkoudrd4n1],
.components-reconnect-repeated-attempt-visible[b-qkoudrd4n1],
.components-reconnect-failed-visible[b-qkoudrd4n1],
.components-pause-visible[b-qkoudrd4n1],
.components-resume-failed-visible[b-qkoudrd4n1],
.components-rejoining-animation[b-qkoudrd4n1] {
    display: none;
}

#components-reconnect-modal.components-reconnect-show .components-reconnect-first-attempt-visible[b-qkoudrd4n1],
#components-reconnect-modal.components-reconnect-show .components-rejoining-animation[b-qkoudrd4n1],
#components-reconnect-modal.components-reconnect-paused .components-pause-visible[b-qkoudrd4n1],
#components-reconnect-modal.components-reconnect-resume-failed .components-resume-failed-visible[b-qkoudrd4n1],
#components-reconnect-modal.components-reconnect-retrying[b-qkoudrd4n1],
#components-reconnect-modal.components-reconnect-retrying .components-reconnect-repeated-attempt-visible[b-qkoudrd4n1],
#components-reconnect-modal.components-reconnect-retrying .components-rejoining-animation[b-qkoudrd4n1],
#components-reconnect-modal.components-reconnect-failed[b-qkoudrd4n1],
#components-reconnect-modal.components-reconnect-failed .components-reconnect-failed-visible[b-qkoudrd4n1] {
    display: block;
}


#components-reconnect-modal[b-qkoudrd4n1] {
    background-color: white;
    width: 20rem;
    margin: 20vh auto;
    padding: 2rem;
    border: 0;
    border-radius: 0.5rem;
    box-shadow: 0 3px 6px 2px rgba(0, 0, 0, 0.3);
    opacity: 0;
    transition: display 0.5s allow-discrete, overlay 0.5s allow-discrete;
    animation: components-reconnect-modal-fadeOutOpacity-b-qkoudrd4n1 0.5s both;
    &[open]

{
    animation: components-reconnect-modal-slideUp-b-qkoudrd4n1 1.5s cubic-bezier(.05, .89, .25, 1.02) 0.3s, components-reconnect-modal-fadeInOpacity-b-qkoudrd4n1 0.5s ease-in-out 0.3s;
    animation-fill-mode: both;
}

}

#components-reconnect-modal[b-qkoudrd4n1]::backdrop {
    background-color: rgba(0, 0, 0, 0.4);
    animation: components-reconnect-modal-fadeInOpacity-b-qkoudrd4n1 0.5s ease-in-out;
    opacity: 1;
}

@keyframes components-reconnect-modal-slideUp-b-qkoudrd4n1 {
    0% {
        transform: translateY(30px) scale(0.95);
    }

    100% {
        transform: translateY(0);
    }
}

@keyframes components-reconnect-modal-fadeInOpacity-b-qkoudrd4n1 {
    0% {
        opacity: 0;
    }

    100% {
        opacity: 1;
    }
}

@keyframes components-reconnect-modal-fadeOutOpacity-b-qkoudrd4n1 {
    0% {
        opacity: 1;
    }

    100% {
        opacity: 0;
    }
}

.components-reconnect-container[b-qkoudrd4n1] {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
}

#components-reconnect-modal p[b-qkoudrd4n1] {
    margin: 0;
    text-align: center;
}

#components-reconnect-modal button[b-qkoudrd4n1] {
    border: 0;
    background-color: #6b9ed2;
    color: white;
    padding: 4px 24px;
    border-radius: 4px;
}

    #components-reconnect-modal button:hover[b-qkoudrd4n1] {
        background-color: #3b6ea2;
    }

    #components-reconnect-modal button:active[b-qkoudrd4n1] {
        background-color: #6b9ed2;
    }

.components-rejoining-animation[b-qkoudrd4n1] {
    position: relative;
    width: 80px;
    height: 80px;
}

    .components-rejoining-animation div[b-qkoudrd4n1] {
        position: absolute;
        border: 3px solid #0087ff;
        opacity: 1;
        border-radius: 50%;
        animation: components-rejoining-animation-b-qkoudrd4n1 1.5s cubic-bezier(0, 0.2, 0.8, 1) infinite;
    }

        .components-rejoining-animation div:nth-child(2)[b-qkoudrd4n1] {
            animation-delay: -0.5s;
        }

@keyframes components-rejoining-animation-b-qkoudrd4n1 {
    0% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    4.9% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 0;
    }

    5% {
        top: 40px;
        left: 40px;
        width: 0;
        height: 0;
        opacity: 1;
    }

    100% {
        top: 0px;
        left: 0px;
        width: 80px;
        height: 80px;
        opacity: 0;
    }
}
/* /Components/Layout/SidebarNav.razor.rz.scp.css */
.sidebar[b-974wm80xms] {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding: 12px 10px;
    background: var(--sidebar);
    color: var(--sidebar-text);
    font-family: var(--font-ui);
}

.nav-section[b-974wm80xms] {
    display: flex;
    flex-direction: column;
    gap: 2px;
}

.section-heading[b-974wm80xms] {
    margin: 14px 0 2px;
    padding: 0 10px;
    font-family: var(--font-ui);
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--muted);
}

.sidebar-footer[b-974wm80xms] {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin-top: auto;
    padding-top: 10px;
    border-top: 1px solid var(--primary-container);
}

.footer-item[b-974wm80xms] {
    display: flex;
    align-items: center;
    gap: 10px;
    min-width: 0;
    padding: 8px 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--sidebar-text);
    font-family: var(--font-ui);
    font-size: 14.5px;
    line-height: 1.3;
    text-align: left;
    text-decoration: none;
    cursor: pointer;
}

.footer-item:hover[b-974wm80xms] {
    background: var(--primary-container);
    color: var(--text);
}

.footer-item .label[b-974wm80xms] {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* The rail's own ☰ (spec §4.1): only at 960–1279px. Its left margin is pinned to the 4px inset
   auto-centring gives it in the 48px rail content box, so widening to the drawer leaves the
   button (and its icon) exactly where the rail shows it. */
.rail-toggle[b-974wm80xms] {
    display: none;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    margin: 0 0 4px 4px;
    padding: 0;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--sidebar-text);
    cursor: pointer;
}

.rail-toggle:hover[b-974wm80xms] {
    background: var(--primary-container);
}

@media (min-width: 960px) and (max-width: 1279.98px) {
    .rail-toggle[b-974wm80xms] {
        display: flex;
    }

    .sidebar[b-974wm80xms] {
        padding: 12px 8px;
    }

    .footer-item[b-974wm80xms] {
        justify-content: center;
        padding: 8px 0;
    }

    /* The drawer's left padding (14px) is the rail's centred icon slot ((48 − 20) / 2), so the
       footer icons stay on the icon column and only their labels appear. */
    html.nav-open .footer-item[b-974wm80xms] {
        justify-content: flex-start;
        padding: 8px 10px 8px 14px;
    }
}

/* Collapse is ≥1280px only (spec §4.2). */
@media (max-width: 1279.98px) {
    .collapse-toggle[b-974wm80xms] {
        display: none;
    }
}
/* /Components/Layout/SkipLink.razor.rz.scp.css */
.skip-link[b-h9t9ylk92j] {
    position: fixed;
    top: -56px;
    left: 12px;
    z-index: 1500;
    padding: 10px 16px;
    background: var(--primary-strong);
    color: #FFFFFF;
    font-family: var(--font-ui);
    font-weight: 600;
    text-decoration: none;
    border-radius: 0 0 8px 8px;
}

.skip-link:focus[b-h9t9ylk92j] {
    top: 0;
}
/* /Components/Layout/ThemeSwitch.razor.rz.scp.css */
.theme-switch[b-rfp23rxske] {
    display: flex;
    align-items: center;
    gap: 10px;
    width: 100%;
    padding: 8px 10px;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--sidebar-text);
    font-family: var(--font-ui);
    font-size: 14.5px;
    line-height: 1.3;
    text-align: left;
    cursor: pointer;
}

.theme-switch:hover[b-rfp23rxske] {
    background: var(--primary-container);
    color: var(--text);
}

/* 960-1279px rail: centred icon with the mode as its tooltip (title comes from the label).
   The drawer's left padding (14px) is the rail's centred icon slot ((48 − 20) / 2), so the icon
   stays put and only its label appears. */
@media (min-width: 960px) and (max-width: 1279.98px) {
    .theme-switch[b-rfp23rxske] {
        justify-content: center;
        padding: 8px 0;
    }

    html.nav-open .theme-switch[b-rfp23rxske] {
        justify-content: flex-start;
        padding: 8px 10px 8px 14px;
    }
}
/* /Components/Layout/TopBar.razor.rz.scp.css */
.topbar[b-lyh992mf8k] {
    align-items: center;
    gap: 10px;
    height: 56px;
    padding: 0 10px;
    background: var(--sidebar);
    color: var(--sidebar-text);
    border-bottom: 1px solid var(--primary-container);
}

.menu-button[b-lyh992mf8k] {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 40px;
    height: 40px;
    flex-shrink: 0;
    padding: 0;
    border: 0;
    border-radius: 8px;
    background: transparent;
    color: var(--sidebar-text);
    cursor: pointer;
}

.menu-button:hover[b-lyh992mf8k] {
    background: var(--primary-container);
}
/* /Components/Pages/Admin/PostAnalyticsPage.razor.rz.scp.css */
.analytics__error[b-4gjfb9i2iq] {
    margin: 1rem 0 0;
    color: var(--primary);
    font-family: var(--font-ui);
}

.analytics__total[b-4gjfb9i2iq] {
    margin: .5rem 0 1.5rem;
    font-family: var(--font-ui);
    font-size: 1.15rem;
}
/* /Components/Pages/Admin/PostEditPage.razor.rz.scp.css */
/* The editor page (spec §6.2): two columns — the form first, then the Preview/Feedback tabs —
   stacking on phones with the form on top. Surfaces use the shared tokens (spec §3.1). */
.post-edit[b-grgveehqka] {
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: 24px;
    align-items: start;
    min-width: 0;
}

@media (min-width: 960px) {
    .post-edit[b-grgveehqka] {
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }
}

.post-edit__form[b-grgveehqka],
.post-edit__side[b-grgveehqka] {
    min-width: 0;
}

.post-edit__error[b-grgveehqka] {
    margin: 0 0 16px;
    padding: 10px 12px;
    border: 1px solid var(--primary);
    border-radius: 6px;
    background: var(--primary-container);
    color: var(--text);
    font-family: var(--font-ui);
    font-size: 14px;
}
/* /Components/Pages/Admin/PostsPage.razor.rz.scp.css */
/* The posts list toolbar (spec §6.1): status chips as toggle buttons in the UI font; the active
   chip is the current filter, so it takes the crimson accent (colour roles, spec §3.1). The
   search field is MudBlazor's markup, so it is styled through ::deep. */
.posts-toolbar[b-ngbpvz33qn] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem 1.25rem;
    margin: 1rem 0 0;
}

.posts-filters[b-ngbpvz33qn] {
    display: flex;
    flex-wrap: wrap;
    gap: 6px;
}

.posts-filters__chip[b-ngbpvz33qn] {
    padding: 3px 12px;
    border: 1px solid var(--muted);
    border-radius: 999px;
    background: none;
    color: var(--text);
    font-family: var(--font-ui);
    font-size: 13px;
    cursor: pointer;
}

.posts-filters__chip--active[b-ngbpvz33qn] {
    border-color: var(--primary);
    background: var(--primary-container);
    color: var(--primary);
    font-weight: 600;
}

.posts-toolbar[b-ngbpvz33qn]  .posts-search {
    min-width: 220px;
}

.posts__error[b-ngbpvz33qn] {
    margin: 1rem 0 0;
    color: var(--primary);
    font-family: var(--font-ui);
}
/* /Components/Pages/Admin/TagsPage.razor.rz.scp.css */
.admin-table__section[b-bucg378zkc] {
    margin-top: 2rem;
}

.admin-table__section h2[b-bucg378zkc] {
    margin: 0 0 .75rem;
    font-family: var(--font-ui);
    font-size: 1.25rem;
}

.admin-table__error[b-bucg378zkc] {
    margin: 1rem 0 0;
    color: var(--primary);
    font-family: var(--font-ui);
}

.admin-table__empty[b-bucg378zkc] {
    margin: .5rem 0 0;
    color: var(--muted);
    font-family: var(--font-ui);
}

.admin-table[b-bucg378zkc] {
    width: 100%;
    margin-top: 1rem;
    border-collapse: collapse;
    font-family: var(--font-ui);
}

.admin-table th[b-bucg378zkc] {
    padding: .5rem .75rem;
    border-bottom: 2px solid var(--primary-container);
    text-align: left;
    font-size: .85rem;
    color: var(--muted);
    font-weight: 600;
}

.admin-table td[b-bucg378zkc] {
    padding: .5rem .75rem;
    border-bottom: 1px solid var(--primary-container);
    vertical-align: middle;
    /* The admin pattern for narrow pages (BannerPicker, MediaGrid, PostEditorForm): long
       names wrap instead of widening the table past the page at 390px. */
    overflow-wrap: anywhere;
}

.admin-table__count[b-bucg378zkc] {
    width: 5rem;
}

.admin-table__switch[b-bucg378zkc] {
    width: 4.5rem;
}

.admin-table__search[b-bucg378zkc] {
    max-width: 22rem;
    margin-top: .5rem;
}

.admin-table__pager[b-bucg378zkc] {
    margin-top: .75rem;
}

.admin-table__name[b-bucg378zkc] {
    margin-right: .5rem;
}

/* The Actions heading is visual noise in a row of buttons; kept for screen readers. */
.admin-table__label[b-bucg378zkc] {
    position: absolute;
    width: 1px;
    height: 1px;
    overflow: hidden;
    clip-path: inset(50%);
    white-space: nowrap;
}

.admin-table__icon[b-bucg378zkc] {
    display: inline-flex;
    padding: .35rem;
    border: 1px solid var(--primary-container);
    border-radius: 8px;
    background: var(--surface);
    color: var(--text);
    cursor: pointer;
}

.admin-table__icon:hover[b-bucg378zkc] {
    background: var(--primary-container);
}

.admin-table__icon:focus-visible[b-bucg378zkc] {
    outline: 2px solid var(--primary);
    outline-offset: 2px;
}

/* Hover target for the tooltip on the Delete button: the padding keeps the pointer on this
   wrapper while the disabled button inside swallows the mouse events. */
.admin-table__actions[b-bucg378zkc] {
    display: inline-flex;
    padding: 4px;
}
/* /Components/Pages/Categories.razor.rz.scp.css */
.category-directory[b-qnmcrz4ntp] {
    list-style: none;
    margin: 1rem 0 0;
    padding: 0;
    display: grid;
    gap: .25rem;
    font-family: var(--font-ui);
}

.category-directory__link[b-qnmcrz4ntp] {
    display: flex;
    align-items: center;
    gap: .6rem;
    padding: .55rem .75rem;
    border-radius: 8px;
    color: inherit;
    text-decoration: none;
}

.category-directory__link:hover[b-qnmcrz4ntp] {
    background: var(--primary-container);
}

.category-directory__name[b-qnmcrz4ntp] {
    font-weight: 600;
}

.category-directory__count[b-qnmcrz4ntp] {
    margin-left: auto;
    color: var(--muted);
    font-size: .85rem;
}

.category-directory__empty[b-qnmcrz4ntp] {
    margin: 1rem 0 0;
    color: var(--muted);
    font-family: var(--font-ui);
}
/* /Components/Pages/Editor/DraftReviewPage.razor.rz.scp.css */
/* The draft review renders the draft with the real post layout, so the same geometry block as the
   post page's Post.razor.css (CSS isolation keeps each copy scoped to its page): text column plus
   the rail when the CONTENT area is at least 1000px wide (the @container query on main), otherwise
   the rail's panels flow after the text and only its TOC is hidden. The measure is the reading
   measure in the READING font (brief 03l item 2): the grid is declared in Source Serif 4 at the
   body text's size (1.125rem, 1.0625rem below 960px — exactly .markdown-body's font), so "ch"
   resolves in the font the text is set in and the text column is clamp(68ch, calc(68ch +
   (100vw - 1440px) * 0.42), 88ch) — ~68 characters of body text per line at ≤1440px growing to
   ~88 at ≥1920px — while the rail keeps its own explicit UI fonts (PostRail.razor.css) and
   width 13.75rem so it scales with the fluid root type. At ≥1000px content width the
   text-plus-rail block is centred under the hero (equal space either side) when it is narrower
   than the hero — replacing 03f's 80rem gate. It stays duplicated: spec §7.1 rule 2 keeps
   component styles in their own scoped .razor.css (only tokens/fonts/the shell grid may live in
   app.css), which cannot be shared between two pages (ruling R5-3-e). Change both files together. */
.post-layout[b-lpuwt14owl] {
    display: grid;
    grid-template-columns: minmax(0, clamp(68ch, calc(68ch + (100vw - 1440px) * 0.42), 88ch));
    gap: 3rem;
    margin-top: 2.5rem;
    padding-bottom: 4rem;
    font-family: var(--font-serif);
    font-size: 1.125rem;
}

/* The measure must track the body text's size, which app.css drops to 1.0625rem on phones. */
@media (max-width: 959.98px) {
    .post-layout[b-lpuwt14owl] {
        font-size: 1.0625rem;
    }
}

@container (min-width: 1000px) {
    .post-layout[b-lpuwt14owl] {
        grid-template-columns: minmax(0, clamp(68ch, calc(68ch + (100vw - 1440px) * 0.42), 88ch)) 13.75rem;
        justify-content: center;
    }

    /* The Related card section sits in the text column below the body and the rail is its own
       column (brief 07d item 3). Both are placed explicitly so the body always takes the text
       column's first row and the section always follows it — with the DOM order body, Related,
       rail, auto-placement would send Related to the rail's column instead. */
    .post-layout[b-lpuwt14owl]  .post-rail {
        grid-column: 2;
        grid-row: 1;
    }

    .post-layout[b-lpuwt14owl]  .related-posts {
        grid-column: 1;
        grid-row: 2;
    }

    /* The rail's top group — Tags, then "On this page" — stays in view while the post scrolls:
       sticky 24px below the viewport top (the window scrolls; no top bar exists at these
       widths) and capped at the viewport height minus the two 24px margins. When the group is
       taller than the cap the TOC is the part that scrolls inside it (Tags stay at the top):
       the Tags panel keeps its height and the TOC flexes into the remaining box with its own
       scrollbar. overflow: hidden makes the cap absolute. */
    .post-layout[b-lpuwt14owl]  .post-rail__group {
        position: sticky;
        top: 24px;
        display: flex;
        flex-direction: column;
        gap: 1.75rem;
        min-width: 0;
        max-height: calc(100vh - 48px);
        overflow: hidden;
    }

    .post-layout[b-lpuwt14owl]  .post-rail__group > .post-rail__panel {
        flex: 0 0 auto;
    }

    .post-layout[b-lpuwt14owl]  .post-rail__group .toc {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
    }
}

@container (max-width: 999.98px) {
    .post-layout[b-lpuwt14owl]  .post-rail {
        display: contents;
    }

    .post-layout[b-lpuwt14owl]  .post-rail .toc {
        display: none;
    }

    /* The rail's children become items of .post-layout's grid when the rail dissolves, and the
       Related section is one already. The wide DOM order — caller panels, then the Tags+TOC
       group, then Related's own section — is not the narrow visual order (Tags, Related, then
       the caller's panels), so the groups take explicit grid order here. With a contents panel
       the tags move into it and the group renders nothing (below). */
    .post-layout[b-lpuwt14owl]  .post-rail__group {
        order: 1;
    }

    .post-layout[b-lpuwt14owl]  .related-posts {
        order: 2;
    }

    .post-layout[b-lpuwt14owl]  .post-rail__extra {
        order: 3;
    }

    /* The contents panel carries the tags in its right-hand column at these widths, so the rail's
       Tags+TOC group does not render after the text for those posts. Posts without a panel (fewer
       than two headings) keep it there as their Tags panel. */
    .post-layout--contents-panel[b-lpuwt14owl]  .post-rail__group {
        display: none;
    }

    /* Headings scrolled to (TOC entries, fragments) must land below the pinned row, never under
       it: scroll-margin-top equals the row's height (ContentsPanel.razor.css). Only posts with
       the panel carry the offset — without one there is no row — and rail widths see neither. */
    .post-layout--contents-panel[b-lpuwt14owl]  .markdown-body :is(h2, h3, h4, h5, h6) {
        scroll-margin-top: 48px;
    }
}

/* The "Add feedback" button at the selection (spec §6.6): an act surface in the crimson accent.
   Absolutely positioned in main's coordinate space (main is the containing block — it carries the
   layout container for the post-rail query), placed by the page from the selection's position
   through the CSSOM (blogEditor.placeSelectionButton). Hidden until that placement lands, so the
   button is never painted at its static position first. */
.draft-review__add-feedback[b-lpuwt14owl] {
    position: absolute;
    z-index: 5;
    visibility: hidden;
    padding: 4px 12px;
    border: 1px solid var(--primary);
    border-radius: 4px;
    background: var(--surface);
    color: var(--primary);
    font-family: var(--font-ui);
    font-size: 12.5px;
    font-weight: 600;
    cursor: pointer;
    box-shadow: 0 1px 4px var(--fallback-wash);
}

.draft-review__add-feedback:hover[b-lpuwt14owl] {
    background: var(--primary-container);
}

.draft-review__error[b-lpuwt14owl] {
    margin: 1rem 0 0;
    color: var(--primary);
    font-family: var(--font-ui);
}
/* /Components/Pages/Editor/DraftsPage.razor.rz.scp.css */
/* The drafts list page: only its error line has page-local styling (the grid styles live with
   PostsGrid). The crimson marks the failure like the admin pages' alerts. */
.drafts__error[b-s0dt5wj6xd] {
    margin: 1rem 0 0;
    color: var(--primary);
    font-family: var(--font-ui);
}
/* /Components/Pages/Post.razor.rz.scp.css */
/* The post page's layout block (scoped CSS cannot reach child component roots, so the rules
   that cross into PostRail/TableOfContents go through ::deep):

   .post-layout { display: grid; grid-template-columns: minmax(0, <measure>); gap: 3rem; }
   @container (min-width: 1000px) {
       .post-layout { grid-template-columns: minmax(0, <measure>) 13.75rem; justify-content: center; }
       .post-rail { grid-column: 2; grid-row: 1 }  .related-posts { grid-column: 1; grid-row: 2 }
       .post-rail__group { position: sticky; top: 24px; max-height: calc(100vh - 48px); … }   <- Tags + TOC
   }
   @container (max-width: 999.98px) {
       .post-rail { display: contents; } .post-rail .toc { display: none; }
       .post-rail__group { order: 1 } .related-posts { order: 2 } .post-rail__extra { order: 3 }
       .post-layout--contents-panel .post-rail__group { display: none }  <- tags live in the panel
       .post-layout--contents-panel .markdown-body h2..h6 { scroll-margin-top: 48px }
                                                                     <- land below the pinned row
   }

   <measure> is the reading measure in the READING font (brief 03l item 2): "ch" is the width of
   "0" in the element's own font, so the grid is declared in Source Serif 4 at the body text's
   size (1.125rem, 1.0625rem below 960px — exactly .markdown-body's font in app.css) and
   clamp(68ch, calc(68ch + (100vw - 1440px) * 0.42), 88ch) means ~68 characters of body text per
   line at ≤1440px growing to ~88 at ≥1920px. The px ramp is brief 03f's trick (calc() cannot
   divide a length by a length): the 20ch of growth is ~200px at the 1920px body size (19.45px;
   Source Serif 4's "0" advances ~0.5em), i.e. ~0.42px per viewport-px over the 1440→1920px
   span; the clamp caps it at 88ch so the value is exact from 1920px up. The rail keeps its own
   explicit UI fonts and shell body size (PostRail.razor.css) and its width is in rem (220px ÷
   16px) so it scales with the fluid root type.

   The container is main (.shell > main, container-type: inline-size), so the query measures the
   content area — viewport minus the sidebar minus main's padding (spec §5.4).

   The text-plus-rail block is centred under the hero whenever the content area is at least
   1000px wide and the block is narrower than the hero: equal space either side (brief 03l item
   2, replacing 03f's 80rem gate). Below 1000px the block keeps its current position and the
   single text column keeps filling the width on phones.

   The same geometry block is duplicated in DraftReviewPage.razor.css (the review page renders the
   post layout too). It stays duplicated: spec §7.1 rule 2 puts each component's styles in its own
   scoped .razor.css and only the tokens/fonts/shell grid in app.css, and CSS isolation cannot
   share one scoped source between two pages (ruling R5-3-e). Change both files together. */
.post-layout[b-ze4fh8vwd3] {
    display: grid;
    grid-template-columns: minmax(0, clamp(68ch, calc(68ch + (100vw - 1440px) * 0.42), 88ch));
    gap: 3rem;
    font-family: var(--font-serif);
    font-size: 1.125rem;
}

/* The measure must track the body text's size, which app.css drops to 1.0625rem on phones. */
@media (max-width: 959.98px) {
    .post-layout[b-ze4fh8vwd3] {
        font-size: 1.0625rem;
    }
}

@container (min-width: 1000px) {
    .post-layout[b-ze4fh8vwd3] {
        grid-template-columns: minmax(0, clamp(68ch, calc(68ch + (100vw - 1440px) * 0.42), 88ch)) 13.75rem;
        justify-content: center;
    }

    /* The Related card section sits in the text column below the body and the rail is its own
       column (brief 07d item 3). Both are placed explicitly so the body always takes the text
       column's first row and the section always follows it — with the DOM order body, Related,
       rail, auto-placement would send Related to the rail's column instead. */
    .post-layout[b-ze4fh8vwd3]  .post-rail {
        grid-column: 2;
        grid-row: 1;
    }

    .post-layout[b-ze4fh8vwd3]  .related-posts {
        grid-column: 1;
        grid-row: 2;
    }

    /* The rail's top group — Tags, then "On this page" — stays in view while the post scrolls:
       sticky 24px below the viewport top (the window scrolls; no top bar exists at these
       widths) and capped at the viewport height minus the two 24px margins. When the group is
       taller than the cap the TOC is the part that scrolls inside it (Tags stay at the top):
       the Tags panel keeps its height and the TOC flexes into the remaining box with its own
       scrollbar. overflow: hidden makes the cap absolute. */
    .post-layout[b-ze4fh8vwd3]  .post-rail__group {
        position: sticky;
        top: 24px;
        display: flex;
        flex-direction: column;
        gap: 1.75rem;
        min-width: 0;
        max-height: calc(100vh - 48px);
        overflow: hidden;
    }

    .post-layout[b-ze4fh8vwd3]  .post-rail__group > .post-rail__panel {
        flex: 0 0 auto;
    }

    .post-layout[b-ze4fh8vwd3]  .post-rail__group .toc {
        flex: 1 1 auto;
        min-height: 0;
        overflow-y: auto;
    }
}

@container (max-width: 999.98px) {
    .post-layout[b-ze4fh8vwd3]  .post-rail {
        display: contents;
    }

    .post-layout[b-ze4fh8vwd3]  .post-rail .toc {
        display: none;
    }

    /* The rail's children become items of .post-layout's grid when the rail dissolves, and the
       Related section is one already. The wide DOM order — caller panels, then the Tags+TOC
       group, then Related's own section — is not the narrow visual order (Tags, Related, then
       the caller's panels), so the groups take explicit grid order here. */
    .post-layout[b-ze4fh8vwd3]  .post-rail__group {
        order: 1;
    }

    .post-layout[b-ze4fh8vwd3]  .related-posts {
        order: 2;
    }

    .post-layout[b-ze4fh8vwd3]  .post-rail__extra {
        order: 3;
    }

    /* The contents panel carries the tags in its right-hand column at these widths, so the rail's
       Tags+TOC group does not render after the text for those posts. Posts without a panel (fewer
       than two headings) keep it there as their Tags panel. */
    .post-layout--contents-panel[b-ze4fh8vwd3]  .post-rail__group {
        display: none;
    }

    /* Headings scrolled to (TOC entries, fragments) must land below the pinned row, never under
       it: scroll-margin-top equals the row's height (ContentsPanel.razor.css). Only posts with
       the panel carry the offset — without one there is no row — and rail widths see neither. */
    .post-layout--contents-panel[b-ze4fh8vwd3]  .markdown-body :is(h2, h3, h4, h5, h6) {
        scroll-margin-top: 48px;
    }
}

/* Page spacing only — the geometry block above is not affected. */
.post-layout[b-ze4fh8vwd3] {
    margin-top: 2.5rem;
    padding-bottom: 4rem;
}
/* /Components/Pages/Search.razor.rz.scp.css */
/* The search field's own look comes from MudBlazor's stylesheet (the MudBlazor classes on the
   plain input and its label). What CSS must say here is what MudBlazor drives from code: the
   filled state's .mud-shrink class toggles on .mud-input, and CSS cannot toggle classes — so
   the same declarations are mirrored from MudBlazor.min.css, keyed on the input having text
   (its placeholder is a single space: :placeholder-shown is false once anything is typed).
   Focused state, hover, borders and the label float all still come from MudBlazor's own rules
   (:focus-within), unchanged. */
.mud-input:has(input:not(:placeholder-shown))~label.mud-input-label.mud-input-label-inputcontrol[b-rt38yofgfk] {
    color: var(--mud-palette-text-primary);
}

.mud-input:has(input:not(:placeholder-shown))~label.mud-input-label.mud-input-label-inputcontrol[b-rt38yofgfk] {
    transform: translate(0, 1.5px) scale(0.75);
    transform-origin: top left;
    max-width: 133.3333333333%;
}

.mud-input:has(input:not(:placeholder-shown))~label.mud-input-label.mud-input-label-inputcontrol.mud-input-label-outlined[b-rt38yofgfk] {
    transform: translate(14px, -6px) scale(0.75);
    max-width: calc((100% - 14px)/.75);
}

.mud-input.mud-input-outlined:has(input:not(:placeholder-shown))>.mud-input-outlined-border legend[b-rt38yofgfk] {
    width: auto;
    padding: 0 5px;
}

/* type=search brings its own clear button and rounded search styling; the field never had a
   clear button and must not grow one now (ruling R15: what the user sees and does is unchanged).
   The Escape key's native "clear the search field" is stopped the same way, in js/shell.js. */
.mud-input-slot[b-rt38yofgfk]::-webkit-search-cancel-button,
.mud-input-slot[b-rt38yofgfk]::-webkit-search-decoration,
.mud-input-slot[b-rt38yofgfk]::-webkit-search-results-button,
.mud-input-slot[b-rt38yofgfk]::-webkit-search-results-decoration {
    display: none;
    -webkit-appearance: none;
}
