/* ---------------------------------------------------------------------------
   App shell — the persistent chrome: brand, primary navigation, and session actions.
   One container in two shapes — a slim left rail on a wide screen, a top bar plus a fixed bottom tab
   bar at phone width — wrapping a scrolling main column, the shared search pill, and the profile chip.
   Backed by Components/Layout/AppShell.razor, MainLayout.razor, and InstallAppButton.razor.
   The chrome is one container in two shapes, so every destination is exactly one element at every width. The
   shell is the app's dark canvas — deep navy, soft shadows, large rounded surfaces (theming.md → Two canvases);
   the live game room inside it is the light table canvas. See docs/design/meta-game.md.
   --------------------------------------------------------------------------- */

/* The app owns the viewport: a fixed shell holding the chrome and the scrolling main column, sitting above
   MainLayout's default scroll container. --content-max caps the content column so its rows don't balloon on a
   wide screen; --app-rail-w is the rail's width and --app-tabbar-h the phone tab bar's band, both read below. */
.app-shell {
    --content-max: 84rem;
    --app-rail-w: 5.5rem;
    --app-tabbar-h: 4.25rem;
    position: fixed;
    inset: 0;
    /* 100svh — the small viewport, the screen with the browser's bars up — rather than the height inset:0 hands
       a fixed element on its own. inset:0 sizes to the layout viewport, which on a phone is the full screen with
       the bars hidden; a shell that tall makes the scrolling main column taller than what is visible, so a page
       that fits the full screen never overflows it and never scrolls under the bars — the play home's three cards
       sat under Safari's toolbar with no way to reach them. The small viewport is the area the player can see,
       and the document itself never scrolls so the bars stay put, which makes it the stable height too. */
    height: 100svh;
    z-index: 1;
    display: flex;
    align-items: stretch;
    background: var(--bg-primary);
}

/* The bottom edge the app's floating toasts stack off, named beside the tab bar's band it clears. Both the
   connection toast (match.css) and the play invite read it, so a phone's tab bar is cleared once rather than
   measured twice. Declared on :root rather than on .app-shell so a toast pinned outside the shell still reads
   it, which is why the tab bar's height is taken through a fallback here. */
:root { --toast-bottom: calc(1rem + env(safe-area-inset-bottom)); }
/* At phone width the chrome's navigation is a fixed tab bar along the bottom edge, and a pill anchored to that
   same edge lands squarely on the middle tab — covering Play, the app's home, with a status message. Clear the
   bar's band. A seated match hides the chrome entirely, so the room keeps the bottom edge. */
@media (max-width: 56rem) {
    :root { --toast-bottom: calc(var(--app-tabbar-h, 4.25rem) + 0.75rem + env(safe-area-inset-bottom)); }
    .app-shell--in-match { --toast-bottom: calc(1rem + env(safe-area-inset-bottom)); }
}

/* An ambient, non-interactive wash behind the whole shell: the room the app is in. A cool bloom high and left
   (where the light comes from), a warm counter-glow opposite it, a barely-there dot weave for texture, and a
   vignette that darkens the corners so the content sits in a pool of light rather than on a flat swatch.
   Fixed so it stays put while the catalog scrolls.

   Four layers, and it is kept to four deliberately. This paints behind every screen in the app at full
   viewport size, so each extra gradient is work every device does on every page: the counter-glow rides in the
   base linear rather than taking a layer of its own, and the vignette carries the base's darkening with it.
   A further glow has to earn that cost in visible atmosphere before it is added. */
.app-shell::before {
    content: "";
    position: fixed;
    inset: 0;
    z-index: -1;
    pointer-events: none;
    background:
        radial-gradient(100% 62% at 22% -12%, color-mix(in srgb, var(--accent-blue) 26%, transparent), transparent 64%),
        radial-gradient(color-mix(in srgb, var(--text-primary) 5%, transparent) 1px, transparent 1.4px),
        radial-gradient(130% 100% at 50% 42%, transparent 38%, rgba(0, 0, 0, 0.34) 100%),
        linear-gradient(155deg,
            color-mix(in srgb, var(--accent-warm) 7%, var(--bg-secondary)) 0%,
            var(--bg-primary) 52%);
    background-size: auto, 30px 30px, auto, auto;
}

/* The keyboard's way past the chrome, drawn only while it holds focus: hidden the way .sr-only hides
   (app.css) until it is focused, then pinned over the top-left corner as a real button-sized target. */
.skip-link {
    position: absolute;
    /* Parked far off the left edge — a plain offset length, unrelated to any radius token that shares the
       literal, so a token sweep never rewrites it into something that is not a <length>. */
    left: -9999px;
    top: 0;
    z-index: var(--z-modal-top);
    padding: 0.7rem 1rem;
    border-radius: 0 0 0.7rem 0;
    background: var(--accent-blue);
    color: var(--text-on-accent);
    font-weight: 600;
    text-decoration: none;
}
.skip-link:focus {
    left: 0;
}

/* --- Main column: the routed page, scrolling beside the chrome -------------- */
.app-main {
    flex: 1 1 auto;
    min-width: 0;
    display: flex;
    flex-direction: column;
    /* The shell above has a definite height (100svh — the visible area, bars up), so the column takes that
       bound rather than re-deriving one: a dvh basis is the full screen with the bars retracted, which is
       taller than the parent and lands the column's last rows below what can be seen. */
    height: 100%;
    overflow-y: auto;
    /* Vertical only. overflow-y:auto alone would compute overflow-x to auto (the visible↔auto coupling), so any
       over-wide child would make this surface — the sticky top bar included — scroll sideways, dragging the whole
       page horizontally. Hide horizontal overflow so a misbehaving child is contained here instead: inner rails
       keep their own overflow-x:auto and scroll internally; the shell never presents a horizontal scrollbar.
       (hidden, not clip: clip resolves to hidden anyway beside a scrolling y axis, and older engines drop it.) */
    overflow-x: hidden;
    overscroll-behavior: contain;
    -webkit-overflow-scrolling: touch;
}

/* The scrolling page content, centered in a capped column so its rows don't balloon on a wide screen. Shared
   by the play home and the games catalog. */
.landing {
    position: relative;
    isolation: isolate;
    display: flex;
    flex-direction: column;
    gap: clamp(1.5rem, 3vw, 2.25rem);
    width: 100%;
    max-width: var(--content-max);
    margin-inline: auto;
    padding: 0.5rem clamp(1rem, 3vw, 2.5rem) calc(2rem + env(safe-area-inset-bottom));
}
/* In the rail shape there is no top chrome bar over the content, so a flat half-rem lands the first row ~8px
   from the top edge of the display and the page reads as if it had been scrolled slightly past its own top.
   Under the phone's top bar the half-rem is right, so the extra height is asked for at the rail's stop — the
   same stop .communities-page uses, so every desktop route starts at one height. */
@media (min-width: 56rem) {
    .landing { padding-top: clamp(1rem, 2.5vh, 2rem); }
}

/* --- Brand (chrome wordmark) ---
   "T&T" monogram: an accent-colored game tile tilted on a second tile peeking out behind it. Both tiles are
   negative-z pseudo-elements so the lettering paints on top of the front tile. */
.brand { display: flex; align-items: center; gap: 0.65rem; min-width: 0; }
.brand__mark {
    position: relative;
    display: flex;
    align-items: center;
    justify-content: center;
    flex-shrink: 0;
    width: 2.6rem;
    height: 2.6rem;
    color: var(--text-on-accent);
    font-size: 0.95rem;
    line-height: 1;
    transform: rotate(-6deg);
}
.brand__mark::before,
.brand__mark::after {
    content: "";
    position: absolute;
    inset: 0;
    border-radius: 0.7rem;
}
.brand__mark::before {
    z-index: -1;
    background: linear-gradient(150deg, color-mix(in srgb, var(--accent-blue) 82%, white), var(--accent-blue));
    box-shadow: 0 6px 14px -6px color-mix(in srgb, var(--accent-blue) 70%, transparent);
}
.brand__mark::after {
    z-index: -2;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    transform: rotate(12deg) translateX(0.2rem);
}

/* --- Chrome: brand + primary navigation + session actions ---
   A slim frosted rail down the left edge: the brand at the top, the three destinations in the middle, and the
   settings gear and profile chip at the foot. At phone width (the ≤56rem block further down) the same container
   lays out as a top bar and the nav detaches into a fixed bottom tab bar. */
.app-chrome {
    position: relative;
    z-index: var(--z-chrome);
    flex: 0 0 var(--app-rail-w);
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 1rem;
    padding-inline: 0.4rem;
    padding-top: calc(0.9rem + env(safe-area-inset-top));
    padding-bottom: calc(0.9rem + env(safe-area-inset-bottom));
    /* Part of the shell's own sky rather than a panel of its own: the ambient wash reads through it, so the
       rail is an edge of the room instead of a slab bolted to the side of it. The hairline and the one-pixel
       inner highlight are what give it an edge at all on a dark canvas.
       The blur radius is kept modest and carries no other filter. A backdrop-filter on a full-height element
       is re-composited on every paint of anything behind it, and this one is behind every screen in the app,
       so a larger radius or a second filter is paid for by every device on every frame — and buys very
       little at this opacity. */
    background: color-mix(in srgb, var(--bg-primary) 62%, transparent);
    -webkit-backdrop-filter: blur(10px);
    backdrop-filter: blur(10px);
    border-right: 1px solid var(--border-color);
    box-shadow: inset -1px 0 0 color-mix(in srgb, var(--text-primary) 5%, transparent);
}
/* The brand link clears the tap-target floor; the 2.6rem mark centers inside the larger hit box. */
.app-chrome__brand { flex: none; min-height: 2.75rem; min-width: 2.75rem; }

/* The app's name beside the monogram. It is drawn only where the chrome is a row with space in its middle —
   the phone top bar — and stood down in the rail, which is a narrow column where the monogram is the whole
   identity. Hidden here and shown in the phone block below, so the default (the rail) needs no exception. */
.brand__wordmark { display: none; }

/* --- The table chip: the table the player is at, carried in the chrome (TableChip.razor) ---
   The chrome's one contextual element. In the rail it is a tile the size of a nav item, sitting under the three
   destinations: a stack of faces over the word "Table", because 5.5rem has no room for a name and a status line.
   The phone shape below fills it out into the top bar's empty middle, where there is room for both. */
.table-chip {
    flex: none;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.25rem;
    min-height: 2.75rem;
    min-width: 2.75rem;
    padding: 0.4rem 0.3rem;
    border-radius: 0.9rem;
    /* At rest the chip is chrome-stock: the teal is reserved for the table you are actually at, so a chip
       pointing somewhere else never out-shouts the nav item marking where you are. */
    background: transparent;
    border: 1px solid var(--border-color);
    color: var(--text-secondary);
    text-decoration: none;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
.table-chip:hover {
    background: color-mix(in srgb, var(--accent-teal) 14%, transparent);
    border-color: color-mix(in srgb, var(--accent-teal) 30%, transparent);
}
/* On the table's own page the chip is a label, not a way somewhere: it stays put (the chrome must not reshuffle
   on the one navigation it exists for), carries the teal that means "this is your table", and stops looking
   pressable — hover included, so the cursor does not re-offer a press that goes nowhere. */
.table-chip--open,
.table-chip--open:hover {
    background: color-mix(in srgb, var(--accent-teal) 24%, transparent);
    border-color: color-mix(in srgb, var(--accent-teal) 30%, transparent);
    color: var(--text-primary);
    cursor: default;
}
.table-chip__faces { display: flex; align-items: center; flex: none; }
.table-chip__avatar {
    --avatar-size: 1.4rem;
    /* A ring in the chrome's own stock, so overlapping discs stay separable rather than merging into one blob. */
    box-shadow: 0 0 0 2px var(--bg-primary);
}
.table-chip__avatar + .table-chip__avatar { margin-left: -0.45rem; }
/* The rail's label. The full sentence lives in the link's accessible name, so nothing is lost by shortening the
   visible one to the word that fits a five-and-a-half-rem column. */
.table-chip__short { font-size: 0.72rem; font-weight: 600; line-height: 1.1; }
/* The name-over-status block belongs to the phone's top bar, which is where the width for it is. */
.table-chip__text { display: none; }

/* The session-action cluster: the settings gear and the profile chip, stacked at the rail's foot (a row at the
   right of the phone top bar). */
.app-chrome__actions {
    display: flex;
    align-items: center;
    flex-direction: column;
    flex-wrap: nowrap;
    gap: 0.5rem;
    margin-top: auto;
}

/* The brand mark is a link to the play home; neutralize anchor defaults so it reads as the logo, not a
   hyperlink. */
a.brand { text-decoration: none; color: inherit; }

/* Primary navigation: Games, Play, Friends. Each destination is an icon over its label so the one markup reads
   as a rail item and as a bottom tab; Friends carries a green online count and a red pending-request bubble.
   Solid icons match the settings gear. */
.app-nav {
    display: flex;
    flex-direction: column;
    align-items: stretch;
    gap: 0.35rem;
    width: 100%;
}
.app-nav__item {
    position: relative;
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 0.2rem;
    /* A comfortable tap target at every width — the rail item and the phone tab share this rule. */
    min-height: 2.75rem;
    min-width: 2.75rem;
    padding: 0.5rem 0.25rem;
    border-radius: 0.9rem;
    color: var(--text-secondary);
    font-size: 0.72rem;
    font-weight: 600;
    text-align: center;
    text-decoration: none;
    transition: color 0.15s ease, background-color 0.15s ease;
}
.app-nav__item .app-icon { width: 1.5rem; height: 1.5rem; }
.app-nav__item:hover { color: var(--accent-blue-ink); background: var(--bg-hover); }
.app-nav__item--active { color: var(--accent-blue-ink); background: color-mix(in srgb, var(--accent-blue) 12%, transparent); }
.app-nav__label { line-height: 1.1; }

/* Play is the app's home, so the current Play tab carries real emphasis — a filled gold tile rather than the
   tint the other destinations use. */
.app-nav__item--play.app-nav__item--active {
    color: var(--text-on-accent-deep);
    background: linear-gradient(180deg, var(--accent-gold-pale), var(--accent-gold) 55%, var(--accent-gold-deep));
    box-shadow: var(--shadow-sm);
}
.app-nav__item--play.app-nav__item--active:hover { filter: brightness(1.05); }

/* The two Friends counters ride in the item's corners (the item is a stacked icon+label, so they sit over the
   icon rather than beside it): the online count top-right, pending requests top-left, so both can show at once. */
.app-nav__badge,
.app-nav__presence {
    position: absolute;
    top: 0.15rem;
    min-width: 1.1rem;
    height: 1.1rem;
    padding: 0 0.25rem;
    display: inline-grid;
    place-items: center;
    border-radius: var(--radius-pill);
    font-size: 0.66rem;
    font-weight: 700;
    line-height: 1;
}
.app-nav__badge {
    left: 0.3rem;
    background: var(--accent-red);
    color: var(--text-on-accent);
}
.app-nav__presence {
    right: 0.3rem;
    background: color-mix(in srgb, var(--accent-green) 16%, transparent);
    color: color-mix(in srgb, var(--accent-green) 75%, var(--text-primary));
}

/* A pill search field shared by the catalog page's in-content search and every host of the "choose a game"
   catalog — the play home's chooser pages, the group's "Start a game" modal, a table's game choice.
   Visual style only — the field's width comes from its host (.landing__search, .game-start-catalog__search). */
.search-field {
    display: flex;
    align-items: center;
    gap: 0.55rem;
    padding: 0 0.9rem;
    border-radius: var(--radius-pill);
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    box-shadow: var(--shadow-sm);
    color: var(--text-muted);
    transition: border-color 0.15s ease, box-shadow 0.15s ease;
}
.search-field:focus-within {
    border-color: color-mix(in srgb, var(--accent-blue) 55%, var(--border-color));
    box-shadow: var(--ring);
}
.search-field__icon { font-size: 0.95rem; flex: none; opacity: 0.7; }
.search-field input {
    flex: 1 1 auto;
    min-width: 0;
    min-height: 2.75rem;
    border: none;
    outline: none;
    background: transparent;
    font-size: 0.95rem;
    color: var(--text-primary);
    /* A placeholder or a typed value wider than the field ends in an ellipsis rather than being cut mid-word. */
    text-overflow: ellipsis;
}
.search-field input::placeholder { color: var(--text-muted); }
.search-field__kbd {
    flex: none;
    font-size: 0.7rem;
    font-weight: 600;
    color: var(--text-muted);
    background: var(--bg-secondary);
    border: 1px solid var(--border-color);
    border-radius: 0.4rem;
    padding: 0.1rem 0.4rem;
}

/* The games page's catalog search: the .search-field pill sized for the scrolling content column. In .landing's
   flex column the field would stretch full width; cap it and left-align so it reads as a focused search bar over
   the catalog rather than spanning the whole wide column. */
.landing__search {
    width: 100%;
    max-width: 34rem;
    align-self: flex-start;
}
/* --- Phone shape: top bar + fixed bottom tab bar ---
   The same chrome container, laid out as a row across the top, with the navigation lifted out of it and pinned
   to the bottom edge as a tab bar — thumb-reachable, and the one place a phone player looks for it. The rail's
   backdrop-filter is dropped here on purpose: a filtered ancestor becomes the containing block for its
   fixed-position descendants, which would trap the tab bar inside the top bar. */
@media (max-width: 56rem) {
    .app-shell { flex-direction: column; }
    .app-chrome {
        flex: none;
        flex-direction: row;
        align-items: center;
        gap: 0.6rem;
        padding-inline: clamp(1rem, 3vw, 1.5rem);
        padding-top: calc(0.7rem + env(safe-area-inset-top));
        padding-bottom: 0.7rem;
        background: color-mix(in srgb, var(--bg-primary) 88%, transparent);
        -webkit-backdrop-filter: none;
        backdrop-filter: none;
        border-right: none;
        border-bottom: 1px solid var(--border-color);
        box-shadow: none;
    }
    .app-chrome__actions {
        flex-direction: row;
        margin-top: 0;
        margin-inline-start: auto;
    }
    /* The name, beside the mark, in the bar's otherwise empty middle. It shrinks and ellipsises rather than
       pushing the session actions off the right edge on a narrow phone. */
    .brand__wordmark {
        display: block;
        min-width: 0;
        overflow: hidden;
        font-size: 1.1rem;
        letter-spacing: 0.01em;
        color: var(--text-primary);
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .app-chrome__brand { flex: 0 1 auto; min-width: 0; }
    /* The table chip takes the bar's middle: a row of faces beside the table's name over what it is doing. It is
       the only thing in the chrome that changes with where the player is, so it gets the width the wordmark was
       holding — an app that can tell you its own name is worth less than one that can tell you your table is
       still there. It shrinks and ellipsises rather than pushing the session actions off the right edge. */
    .table-chip {
        flex: 1 1 auto;
        flex-direction: row;
        justify-content: flex-start;
        gap: 0.5rem;
        min-width: 0;
        padding: 0.3rem 0.6rem;
        border-radius: var(--radius-pill);
    }
    .table-chip__short { display: none; }
    .table-chip__text { display: flex; flex-direction: column; min-width: 0; }
    .table-chip__name {
        font-size: 0.85rem;
        font-weight: 700;
        line-height: 1.15;
        color: var(--text-primary);
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    .table-chip__status {
        font-size: 0.7rem;
        line-height: 1.15;
        overflow: hidden;
        text-overflow: ellipsis;
        white-space: nowrap;
    }
    /* The app's own name yields to the table: both want the bar's middle, and only one of them is news. The
       monogram stays, so the way home is still there — but the brand link has to be given its tap floor back
       along with it. The phone shape lets the brand shrink (min-width:0) so a long wordmark ellipsises instead
       of pushing the session actions off the right edge; with the wordmark gone there is nothing left to shrink
       *to* except the 2.6rem monogram, which lands the whole link under 44px. */
    .app-chrome:has(.table-chip) .brand__wordmark { display: none; }
    .app-chrome:has(.table-chip) .app-chrome__brand { flex: none; min-width: 2.75rem; }
    .app-main {
        /* 100svh — the small viewport — as a definite basis, so WebKit does not size this overflow:auto flex
           item to its own content. The bound on what is visible comes from the shell (100svh above): this
           column's flex-shrink seats it under the top chrome, leaving a box the height of the visible area
           minus that chrome, and a page taller than that overflows it and scrolls. */
        height: 100svh;
        flex: 1 1 auto;
        min-height: 0;
        /* Reserve the tab bar's band so the last row of a page is never trapped under it. */
        padding-bottom: calc(var(--app-tabbar-h) + env(safe-area-inset-bottom));
    }
    /* No chrome while a match is on, so nothing to reserve for. */
    .app-shell--in-match .app-main { padding-bottom: 0; }
    .app-nav {
        position: fixed;
        inset-inline: 0;
        bottom: 0;
        z-index: var(--z-chrome);
        flex-direction: row;
        align-items: stretch;
        justify-content: space-around;
        gap: 0.25rem;
        height: calc(var(--app-tabbar-h) + env(safe-area-inset-bottom));
        padding: 0.35rem 0.75rem calc(0.35rem + env(safe-area-inset-bottom));
        /* The tab bar is the one chrome surface the page scrolls under, so it is nearly — not fully — opaque:
           a hint of the room moves beneath it. Deliberately no backdrop-filter. It is a fixed band over a
           scrolling surface, which is the most expensive place in the app to put one, and at this opacity
           there is almost nothing behind it left to blur. */
        background: color-mix(in srgb, var(--bg-primary) 94%, transparent);
        border-top: 1px solid var(--border-color);
    }
    .app-nav__item { flex: 1 1 0; }
}

.profile-chip {
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0.2rem;
    min-height: 2.75rem;
    min-width: 2.75rem;
    border-radius: var(--radius-pill);
    background: var(--bg-card);
    border: 1px solid var(--border-color);
    color: var(--text-primary);
    box-shadow: var(--shadow-sm);
    cursor: pointer;
    transition: background-color 0.15s ease, border-color 0.15s ease;
}
.profile-chip:hover { background: var(--bg-hover); border-color: color-mix(in srgb, var(--accent-blue) 30%, var(--border-color)); }
.profile-chip__avatar { --avatar-size: var(--avatar-sm); }

/* The glyph-shaped variant of the chip — the settings gear and the install button, which carry a character
   rather than an avatar disc. The tap floor is the base chip's min-size, so the padding here only sets how much
   room the glyph gets inside it. */
.profile-chip--icon {
    padding: 0.4rem 0.6rem;
    font-size: 1.1rem;
    line-height: 1;
}
@media (max-width: 30rem) {
    /* Trim the chip's padding and glyph on the narrowest screens so the action cluster stays compact beside the
       brand; the tap-target floor (the min-size on .profile-chip) holds regardless. */
    .app-chrome__actions { gap: 0.35rem; }
    .profile-chip--icon { padding: 0.35rem 0.45rem; font-size: 1rem; }
}

/* ---------------------------------------------------------------------------
   Fallback surfaces: the unhandled-error page (Components/Pages/Error.razor) and the router's not-found
   view (Components/App.razor). Both render into MainLayout's content region without the app shell around
   them, so they carry their own type scale and spacing. Deliberately plain — these are the screens shown
   when something has already gone wrong, so they lean on the tokens and add no flourish of their own.
   --------------------------------------------------------------------------- */

.error-page {
    padding: 1.5rem;
    border-radius: 0.5rem;
    background: var(--bg-card);
    border: 1px solid var(--border-color);
}

.error-page__title {
    font-size: 1.25rem;
    line-height: 1.75rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
    color: var(--accent-red-ink);
}

.error-page__lead {
    text-wrap: pretty;
    margin-bottom: 1rem;
    color: var(--text-secondary);
}

/* The request-id line and the deployment warning: small, quiet, diagnostic. */
.error-page__note {
    font-size: 0.875rem;
    line-height: 1.25rem;
    color: var(--text-muted);
}

.error-page__id {
    padding-inline: 0.375rem;
    padding-block: 0.125rem;
    border-radius: 0.25rem;
    background: var(--bg-hover);
}

/* The development-mode guidance, ruled off from the error itself. */
.error-page__dev {
    margin-top: 1.5rem;
    padding-top: 1rem;
    border-top: 1px solid var(--border-color);
}

.error-page__dev-title {
    font-size: 0.875rem;
    line-height: 1.25rem;
    font-weight: 500;
    margin-bottom: 0.5rem;
    color: var(--text-primary);
}

.error-page__dev-note {
    font-size: 0.875rem;
    line-height: 1.25rem;
    margin-bottom: 0.5rem;
    color: var(--text-secondary);
}

.notfound-page {
    padding: 1.5rem;
    text-align: center;
}

.notfound-page__title {
    text-wrap: balance;
    font-size: 1.25rem;
    line-height: 1.75rem;
    font-weight: 600;
    margin-bottom: 0.5rem;
    color: var(--text-primary);
}

.notfound-page__blurb {
    color: var(--text-secondary);
}

