/* /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;
}

/* From 960px (where the editor has two columns) the box fills the viewport height, less the
   toolbar and a margin, so it is about as tall as the Preview beside it; the user can still
   resize it. Under 960px it keeps the 320px minimum above and the rows-based height. */
@media (min-width: 960px) {
    .md-editor__textarea[b-293b75xr78] {
        height: max(320px, calc(100dvh - 12rem));
    }
}

.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-placement[b-tou7bkawe8] {
    display: flex;
    flex-direction: column;
    gap: 4px;
    flex: 1 1 180px;
    min-width: 0;
}

.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/PostsList.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-prgktw06y3] {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: .75rem 1.25rem;
    margin: 1rem 0 0;
}

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

.posts-filters__chip[b-prgktw06y3] {
    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-prgktw06y3] {
    border-color: var(--primary);
    background: var(--primary-container);
    color: var(--primary);
    font-weight: 600;
}

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

.posts__error[b-prgktw06y3] {
    margin: 1rem 0 0;
    color: var(--primary);
    font-family: var(--font-ui);
}
/* /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;
}

/* The icon in the top-left corner, 8px in (the related thumbnails). */
.banner-or-fallback--icon-corner .banner-or-fallback__icon[b-v2ymqs9thu] {
    inset: 8px auto auto 8px;
}

.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%. Brief 09e item 3: while
   it is open it also raises its layer above the post's tags and everything else the article can
   draw — the article's own stacking tops out at z-index 5 today (Post's hero/cards 1–3,
   DraftReviewPage's add-feedback 5) and 40 clears it with headroom; nothing else here lives
   between the scrim (20) and 40, and the scrim must stay under the drawer either way. */
.toc-inline--open[b-o3fwpvs1wm] {
    box-shadow: 0 4px 16px rgb(0 0 0 / 24%);
    z-index: 40;
}

/* The one-row bar: the current section's title and a chevron that doubles as the pull handle.
   Its own touch-action so a drag anywhere on the row pulls the drawer instead of scrolling the
   page (brief 09e item 4: a reader pulls where the text is) — the row is only ever shown pinned
   or as the drawer's header, so page scrolling elsewhere is untouched. */
.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;
    /* WORKAROUND(W-016, Browsers): touch-action none so a drag on the outline row, handle or strip pulls the drawer instead of scrolling the page */
    touch-action: none;
    /* END WORKAROUND(W-016) */
}

.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);
    /* WORKAROUND(W-016, Browsers): touch-action none so a drag on the outline row, handle or strip pulls the drawer instead of scrolling the page */
    touch-action: none;
    /* END WORKAROUND(W-016) */
    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. */
/* The list block (title, list, "+N more…", tags) fades with the block's height instead of
   popping: when the block collapses to the row or grows back, every frame shows the row or the
   list — never an empty area under the row. The opacity transition runs exactly alongside the
   height's (same 200ms, same easing), so the two stay in lockstep: by the time the block is
   more than 8px taller than the row, the list is at least faintly painted; by the time it
   fades out, the block is already at the row's height. visibility rides along (discrete: it
   flips to hidden only at the fade-out's end, to visible at the fade-in's start) so a hidden
   list leaves the tab order, exactly as display:none did. The lockstep's domain: height and
   opacity share one eased progress, so gap > 8px and opacity < 0.05 can only coexist when the
   block is more than 160px taller than the row (8px / 5%); posts whose block grows taller than
   that (a very long tag column) can see a violating frame of a few milliseconds at 3–5% opacity
   — measured bound, brief 09e's final review. */
.toc-inline__body[b-o3fwpvs1wm] {
    display: flex;
    align-items: flex-start;
    gap: 16px;
    opacity: 1;
    visibility: visible;
    transition: opacity 200ms ease, visibility 200ms ease;
}

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

.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%;
}

/* 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;
    /* WORKAROUND(W-016, Browsers): touch-action none so a drag on the outline row, handle or strip pulls the drawer instead of scrolling the page */
    touch-action: none;
    /* END WORKAROUND(W-016) */
    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 40 while the drawer is open, 30 pinned/closed) 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__body[b-o3fwpvs1wm],
    .toc-inline__chevron[b-o3fwpvs1wm],
    .toc-inline__scrim[b-o3fwpvs1wm] {
        transition: none;
    }
}
/* /Components/Blog/Masthead.razor.rz.scp.css */
/* The site masthead (brief 10b item 3): a slim strip above "Latest Posts" on the home page's
   first page. Layers, bottom to top: the banner image tiled at 180px (the masthead's own
   --masthead-banner, else --fallback-banner: the same tile as the cards' fallback), the
   fallback wash (::before), a dark gradient for the text (::after, the Featured card's overlay
   gradient) and the text. About 180px tall from 960px wide, about 120px below; the height is a
   minimum, so text that wraps grows the strip and never truncates the name. */
.masthead[b-dpixjz83ls] {
    position: relative;
    display: flex;
    align-items: flex-end;
    box-sizing: border-box;
    min-height: 120px;
    margin: 0 0 1.25rem;
    padding: 14px 18px;
    overflow: hidden;
    border-radius: 10px;
    background-image: var(--masthead-banner, var(--fallback-banner));
    background-position: 0 0;
    background-size: 180px;
    background-repeat: repeat;
    box-shadow: 0 1px 3px rgba(60, 20, 30, .15);
}

.masthead[b-dpixjz83ls]::before {
    content: "";
    position: absolute;
    inset: 0;
    background: var(--fallback-wash);
}

.masthead[b-dpixjz83ls]::after {
    content: "";
    position: absolute;
    inset: 0;
    background: linear-gradient(to top, rgba(0, 0, 0, .75), rgba(0, 0, 0, 0) 65%);
}

.masthead__text[b-dpixjz83ls] {
    position: relative;
    z-index: 1;
    min-width: 0;
}

.masthead__name[b-dpixjz83ls] {
    margin: 0;
    overflow-wrap: break-word;
    font-family: var(--font-serif);
    font-size: 1.75rem;
    line-height: 1.2;
    color: #FFFFFF;
}

.masthead__description[b-dpixjz83ls] {
    margin: 4px 0 0;
    overflow-wrap: break-word;
    font-size: 1rem;
    line-height: 1.4;
    color: #FFFFFF;
}

@media (min-width: 960px) {
    .masthead[b-dpixjz83ls] {
        min-height: 180px;
        padding: 20px 28px;
    }

    .masthead__name[b-dpixjz83ls] {
        font-size: 2.25rem;
    }

    .masthead__description[b-dpixjz83ls] {
        font-size: 1.125rem;
    }
}
/* /Components/Blog/NotFoundContent.razor.rz.scp.css */
/* The not-found copy as a static document draws it: the reading pages load no MudBlazor, so
   the heading and the two text buttons are plain elements styled here to the same look the
   MudBlazor rendering has (a 3rem serif heading; upper-case 500-weight links in the accent
   colour, washed on hover, focus and press; keyboard focus adds the outline from app.css). */
.not-found__title[b-267gmg3rw1] {
    margin: 0 0 8px;
    font-family: var(--font-serif);
    font-size: 3rem;
    font-weight: 400;
    line-height: 1.167;
    letter-spacing: 0;
}

.not-found__link[b-267gmg3rw1] {
    position: relative;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 64px;
    padding: 6px 8px;
    overflow: hidden;
    border-radius: 4px;
    background-color: transparent;
    color: var(--primary);
    cursor: pointer;
    font-family: var(--font-ui);
    font-size: .875rem;
    font-weight: 500;
    line-height: 1.75;
    letter-spacing: .02857em;
    text-decoration: none;
    text-transform: uppercase;
    transition: background-color 250ms cubic-bezier(0.4, 0, 0.2, 1), box-shadow 250ms cubic-bezier(0.4, 0, 0.2, 1), border 250ms cubic-bezier(0.4, 0, 0.2, 1);
    user-select: none;
    vertical-align: middle;
}

.not-found__link:focus-visible[b-267gmg3rw1],
.not-found__link:active[b-267gmg3rw1] {
    background-color: color-mix(in srgb, var(--primary) 5.88235%, transparent);
}

@media (hover: hover) and (pointer: fine) {
    .not-found__link:hover[b-267gmg3rw1] {
        background-color: color-mix(in srgb, var(--primary) 5.88235%, transparent);
    }
}
/* /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;
}

/* WORKAROUND(W-039, MudBlazor): MudBlazor's global a reset removes the underline, so post-body links are underlined again */
/* 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;
}
/* END WORKAROUND(W-039) */

.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] {
    /* The width comes from the card: without it, max-height carries through the ratio into a
       max-width of 420 * 21 / 9 = 980px and a wider card shows white beside the image. */
    width: 100%;
    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;
    /* WORKAROUND(W-057, Browsers): multi-line clamping uses the prefixed -webkit-box model */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    /* END WORKAROUND(W-057) */
    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;
    /* WORKAROUND(W-057, Browsers): multi-line clamping uses the prefixed -webkit-box model */
    display: -webkit-box;
    -webkit-line-clamp: 2;
    -webkit-box-orient: vertical;
    /* END WORKAROUND(W-057) */
    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;
    /* WORKAROUND(W-057, Browsers): multi-line clamping uses the prefixed -webkit-box model */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    /* END WORKAROUND(W-057) */
    overflow: hidden;
}

.post-card--featured .post-card__summary[b-lt8bydhzm8] {
    /* WORKAROUND(W-057, Browsers): multi-line clamping uses the prefixed -webkit-box model */
    -webkit-line-clamp: 3;
    /* END WORKAROUND(W-057) */
}

.post-card--list .post-card__summary[b-lt8bydhzm8] {
    /* WORKAROUND(W-057, Browsers): multi-line clamping uses the prefixed -webkit-box model */
    -webkit-line-clamp: 2;
    /* END WORKAROUND(W-057) */
}

.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] {
    /* WORKAROUND(W-057, Browsers): multi-line clamping uses the prefixed -webkit-box model */
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    /* END WORKAROUND(W-057) */
    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 */
/* WORKAROUND-FILE(W-002, Blazor): styles for the copied template reconnect modal (class names are the framework contract) */
.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;
    /* Explicit: app.css gives every <button> justify-content: center, so the Collapse button
       would centre its content where the Log in link (an <a>) starts at the left. */
    justify-content: flex-start;
    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/SiteFooter.razor.rz.scp.css */
/* One quiet line (two on a phone): UI font at the sidebar's label size, muted text, links in the
   link colour. It follows the page content, so it is neither fixed nor sticky. */
.site-footer[b-zq833o5tqm] {
    margin-top: 48px;
    padding-top: 16px;
    border-top: 1px solid color-mix(in srgb, var(--text) 14%, transparent);
    color: var(--muted);
    font-family: var(--font-ui);
    font-size: 14.5px;
    line-height: 1.5;
}

.site-footer a[b-zq833o5tqm] {
    color: var(--primary);
}

.site-footer__sep[b-zq833o5tqm] {
    color: var(--muted);
}
/* /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;
    /* Explicit: app.css centres every <button>'s flex content (see .footer-item). */
    justify-content: flex-start;
    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;
}

/* WORKAROUND(W-086, Blazor): no circuit re-renders the button on static pages; data-mode picks the icon */
/* Only the icon of the current mode shows. */
.theme-switch__icon[b-rfp23rxske] {
    display: none;
}

.theme-switch[data-mode="system"] .theme-switch__icon--system[b-rfp23rxske],
.theme-switch[data-mode="light"] .theme-switch__icon--light[b-rfp23rxske],
.theme-switch[data-mode="dark"] .theme-switch__icon--dark[b-rfp23rxske] {
    display: inline-flex;
}

/* END WORKAROUND(W-086) */

.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] {
        --post-edit-top-gap: 24px;
        grid-template-columns: minmax(0, 1fr) minmax(0, 1fr);
    }

    /* From 960px the editor fits the viewport instead of growing with the Preview: the Preview
       column parks below the page's top gap while the form scrolls, is as tall as the viewport
       below that gap, and scrolls on its own (editor.js follows the Markdown box's top line
       into it). Under 960px the columns stack and nothing here applies. */
    .post-edit__side[b-grgveehqka] {
        position: sticky;
        top: var(--post-edit-top-gap);
        height: calc(100dvh - var(--post-edit-top-gap));
        overflow-y: auto;
    }
}

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

.post-edit__notice[b-grgveehqka] {
    margin: 0 0 16px;
    padding: 10px 12px;
    border: 1px solid var(--muted);
    border-radius: 6px;
    background: var(--surface);
    color: var(--text);
    font-family: var(--font-ui);
    font-size: 14px;
}

.post-edit__notice-dismiss[b-grgveehqka] {
    margin-left: 8px;
    padding: 2px 8px;
    border: 1px solid var(--muted);
    border-radius: 4px;
    background: transparent;
    color: var(--text);
    font: inherit;
    cursor: pointer;
}

.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/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);
}

/* With the contents panel, the panel's own margin-bottom carries the gap below it (the same
   2.5rem, now the block's padding — which never collapses). Adjacent siblings' vertical margins
   collapse (to their maximum, or the positive-plus-negative sum), so a margin-top here would
   silently replace the panel's shrinking margin-bottom and break the panel's
   "height + margin-bottom = footprint" rule (ContentsPanel.razor.css, post.js): every state
   change would shift the page and scroll anchoring would drag the scroll position with it.
   Posts without a panel keep the margin above. Change this file and Post's together. */
.post-layout--contents-panel[b-lpuwt14owl] {
    margin-top: 0;
    padding-top: 2.5rem;
}
/* /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;
}

/* With the contents panel, the panel's own margin-bottom carries the gap below it (the same
   2.5rem, now the block's padding — which never collapses). Adjacent siblings' vertical margins
   collapse (to their maximum, or the positive-plus-negative sum), so a margin-top here would
   silently replace the panel's shrinking margin-bottom and break the panel's
   "height + margin-bottom = footprint" rule (ContentsPanel.razor.css, post.js): every state
   change would shift the page and scroll anchoring would drag the scroll position with it.
   Posts without a panel keep the margin above. Change this file and DraftReviewPage's together. */
.post-layout--contents-panel[b-ze4fh8vwd3] {
    margin-top: 0;
    padding-top: 2.5rem;
}
/* /Components/Pages/Search.razor.rz.scp.css */
/* The search field (spec §5.3): an outlined field with a floating label and a search button at
   its end. The reading pages load no MudBlazor stylesheet, so the field is drawn here, in the
   page's own classes, from the site's tokens (theme.css). Everything is one component: the
   label floats on :focus-within (focused) and on :has(input:not(:placeholder-shown)) (holding
   text; the placeholder is a single space, so :placeholder-shown is false once anything is
   typed) — CSS cannot toggle classes, so no class says "filled". */

/* Three tones the site's tokens do not carry (the field's hairline, its icon and the icon's
   hover wash) as theme-keyed custom properties — the same shape as app.css's --scrim: light,
   explicit dark, system dark. They are the values the field has always drawn with. */
.search-field[b-rt38yofgfk] {
    --field-line: rgb(189 189 189);
    --field-icon: rgb(0 0 0 / .5372549);
    --field-icon-wash: rgb(0 0 0 / .0588235);
}

:root[data-theme=dark] .search-field[b-rt38yofgfk] {
    --field-line: rgb(255 255 255 / .2980392);
    --field-icon: rgb(173 173 177);
    --field-icon-wash: rgb(173 173 177 / .0588235);
}

@media (prefers-color-scheme: dark) {
    :root:not([data-theme=light]) .search-field[b-rt38yofgfk] {
        --field-line: rgb(255 255 255 / .2980392);
        --field-icon: rgb(173 173 177);
        --field-icon-wash: rgb(173 173 177 / .0588235);
    }
}

.search-field[b-rt38yofgfk] {
    position: relative;
    display: flex;
    flex-direction: column;
    max-width: 100%;
    margin: 8px 0 16px;
    user-select: none;
}

.search-field__box[b-rt38yofgfk] {
    position: relative;
    display: inline-flex;
    align-items: center;
    padding-right: 14px;
    color: var(--text);
    color-scheme: light;
    cursor: text;
    font-family: var(--font-serif);
    font-size: 1rem;
    font-weight: 400;
    line-height: 1.1876em;
    letter-spacing: .00938em;
}

.search-field__input[b-rt38yofgfk] {
    position: relative;
    display: block;
    box-sizing: content-box;
    width: 100%;
    min-width: 0;
    height: 1lh;
    margin: 0;
    padding: 18.5px 0 18.5px 14px;
    border: 0;
    background: none;
    color: currentcolor;
    font: inherit;
    outline: 0;
}

/* WORKAROUND(W-003, Browsers): type=search brings a native clear button and decorations in WebKit and Blink, hidden here */
/* 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. */
.search-field__input[b-rt38yofgfk]::-webkit-search-cancel-button,
.search-field__input[b-rt38yofgfk]::-webkit-search-decoration,
.search-field__input[b-rt38yofgfk]::-webkit-search-results-button,
.search-field__input[b-rt38yofgfk]::-webkit-search-results-decoration {
    display: none;
    -webkit-appearance: none;
}
/* END WORKAROUND(W-003) */

.search-field__adornment[b-rt38yofgfk] {
    display: flex;
    align-items: center;
    max-height: 2em;
    height: .01em;
    margin-left: 8px;
    white-space: nowrap;
}

.search-field__button[b-rt38yofgfk] {
    position: relative;
    display: inline-flex;
    flex: 0 0 auto;
    align-items: center;
    justify-content: center;
    margin: 0 -12px 0 0;
    padding: 12px;
    border: 0;
    border-radius: 50%;
    background-color: transparent;
    color: var(--field-icon);
    cursor: pointer;
    font-size: 1.5rem;
    overflow: hidden;
    text-align: center;
    transition: background-color 150ms cubic-bezier(0.4, 0, 0.2, 1);
    user-select: none;
    vertical-align: middle;
    appearance: none;
    -webkit-tap-highlight-color: transparent;
}

.search-field__button:focus-visible[b-rt38yofgfk],
.search-field__button:active[b-rt38yofgfk] {
    background-color: var(--field-icon-wash);
}

@media (hover: hover) and (pointer: fine) {
    .search-field__button:hover[b-rt38yofgfk] {
        background-color: var(--field-icon-wash);
    }
}

.search-field__icon[b-rt38yofgfk] {
    display: inline-block;
    flex-shrink: 0;
    width: 1em;
    height: 1em;
    fill: currentcolor;
    transition: fill 200ms cubic-bezier(0.4, 0, 0.2, 1);
    user-select: none;
}

.search-field__border[b-rt38yofgfk] {
    position: absolute;
    top: 0;
    right: 0;
    left: 0;
    display: flex;
    box-sizing: border-box;
    width: 100%;
    max-width: 100%;
    min-width: 0;
    height: 100%;
    margin: 0;
    padding: 0;
    border: 1px solid var(--field-line);
    border-radius: 4px;
    pointer-events: none;
    text-align: start;
    transition: border-width, border-color 200ms cubic-bezier(0.4, 0, 0.2, 1);
}

/* The notch the label floats into: collapsed while the label sits in the field, opened to the
   label's width when it floats. */
.search-field__border legend[b-rt38yofgfk] {
    position: relative;
    float: none;
    visibility: hidden;
    width: 0;
    height: 0;
    margin: 0 11px;
    padding: 0;
    font-size: .75rem;
    font-weight: inherit;
}

@media (hover: hover) and (pointer: fine) {
    .search-field__box:not(:focus-within):hover .search-field__border[b-rt38yofgfk] {
        border-color: var(--field-icon);
    }
}

.search-field__box:focus-within > .search-field__border[b-rt38yofgfk] {
    border-width: 2px;
    border-color: var(--primary);
}

.search-field__box:focus-within > .search-field__border legend[b-rt38yofgfk],
.search-field__box:has(input:not(:placeholder-shown)) > .search-field__border legend[b-rt38yofgfk] {
    width: auto;
    padding: 0 5px;
}

.search-field__label[b-rt38yofgfk] {
    position: absolute;
    top: 0;
    left: 0;
    z-index: 0;
    display: block;
    max-width: calc(100% - 14px);
    padding: 0 5px;
    overflow: hidden;
    color: var(--muted);
    font-family: var(--font-serif);
    font-size: 1rem;
    font-weight: 400;
    line-height: 1.15rem;
    letter-spacing: .00938em;
    pointer-events: none;
    text-overflow: ellipsis;
    transform: translate(14px, 20px) scale(1);
    transform-origin: left top;
    white-space: nowrap;
    transition: color 200ms cubic-bezier(0, 0, 0.2, 1), transform 200ms cubic-bezier(0, 0, 0.2, 1), max-width 200ms cubic-bezier(0, 0, 0.2, 1);
}

.search-field__box:focus-within ~ .search-field__label[b-rt38yofgfk],
.search-field__box:has(input:not(:placeholder-shown)) ~ .search-field__label[b-rt38yofgfk] {
    max-width: calc(133.333% - 18.6667px);
    transform: translate(14px, -6px) scale(.75);
}

.search-field__box:focus-within ~ .search-field__label[b-rt38yofgfk] {
    color: var(--primary);
}

.search-field__box:has(input:not(:placeholder-shown)) ~ .search-field__label[b-rt38yofgfk] {
    color: var(--text);
}
