html, body {
    margin: 0;
    padding: 0;
    background: var(--p1-bg);
    color: var(--p1-text-primary);
    font-family: var(--p1-font-family);
    -webkit-font-smoothing: antialiased;
}

* {
    box-sizing: border-box;
}

a, a:visited {
    color: inherit;
    text-decoration: none;
}

button {
    font-family: inherit;
    appearance: none;
    -webkit-appearance: none;
    background: none;
    border: none;
    color: inherit;
    cursor: pointer;
}

:focus-visible {
    outline: 2px solid var(--p1-blue);
    outline-offset: 2px;
}

[tabindex="-1"]:focus {
    outline: none;
}

/* ── Unhandled-error toast ──
   Blazor flips this to `display: block` inline when an error occurs, so the layout lives on
   .err-inner. Presented as a floating card rather than a full-bleed red bar. */
#blazor-error-ui {
    display: none;
    position: fixed;
    left: 50%;
    transform: translateX(-50%);
    bottom: calc(1rem + env(safe-area-inset-bottom, 0px));
    width: min(30rem, calc(100% - 2rem));
    z-index: 1000;
}

#blazor-error-ui .err-inner {
    display: flex;
    align-items: center;
    gap: 0.75rem;
    padding: 0.875rem 1rem;
    border-radius: 0.875rem;
    background: var(--p1-surface);
    border: 1px solid rgba(var(--p1-accent-red-rgb), 0.35);
    box-shadow: 0 12px 32px rgba(0, 0, 0, 0.45);
}

#blazor-error-ui .err-icon {
    display: flex;
    align-items: center;
    justify-content: center;
    width: 2rem;
    height: 2rem;
    border-radius: 0.625rem;
    flex-shrink: 0;
    background: rgba(var(--p1-accent-red-rgb), 0.15);
    color: var(--p1-neg-text);
}

#blazor-error-ui .err-text {
    display: flex;
    flex-direction: column;
    flex: 1;
    min-width: 0;
    line-height: 1.35;
}

#blazor-error-ui .err-text strong {
    font-size: var(--p1-text-md);
    font-weight: 700;
    color: var(--p1-text-primary);
}

#blazor-error-ui .err-sub {
    font-size: var(--p1-text-xs);
    color: var(--p1-text-secondary);
}

#blazor-error-ui .reload {
    flex-shrink: 0;
    padding: 0.4375rem 0.75rem;
    border-radius: 0.5rem;
    background: var(--p1-accent-active);
    color: var(--p1-text-on-accent);               /* on red — stays white in light mode */
    font-size: var(--p1-text-sm);
    font-weight: 600;
    text-decoration: none;
}

#blazor-error-ui .reload:hover {
    background: var(--p1-brand-btn-hover);
}

#blazor-error-ui .dismiss {
    flex-shrink: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    width: 1.75rem;
    height: 1.75rem;
    border: none;
    background: none;
    border-radius: 0.5rem;
    color: var(--p1-text-muted);
    cursor: pointer;
}

#blazor-error-ui .dismiss:hover {
    background: var(--p1-surface-inner);
    color: var(--p1-text-primary);
}

/* The err-sub is the first thing to go when there is no room for it. */
@media (max-width: 400px) {
    #blazor-error-ui .err-sub { display: none; }
}

/* ── PageLoader (global — the pre-boot splash in index.html renders before Blazor
      exists, so it carries no CSS-isolation scope attribute and can only match
      global rules. PageLoader.razor reuses these same classes.) ── */
@keyframes p1-loader-rotate {
    to { transform: rotate(360deg); }
}

/* Sweeps a wedge around the circle so each ring reads as being drawn and erased,
   rather than a solid ring merely spinning. */
@keyframes p1-loader-clip {
    0%   { clip-path: polygon(50% 50%, 0 0, 0 0, 0 0, 0 0, 0 0); }
    25%  { clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 0, 100% 0, 100% 0); }
    50%  { clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 100% 100%, 100% 100%); }
    75%  { clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 100%); }
    100% { clip-path: polygon(50% 50%, 0 0, 100% 0, 100% 100%, 0 100%, 0 0); }
}

.p1-loader {
    width: 48px;
    height: 48px;
    border-radius: 50%;
    position: relative;
    animation: p1-loader-rotate 1s linear infinite;
}

.p1-loader::before,
.p1-loader::after {
    content: "";
    box-sizing: border-box; /* the 5px border must sit inside the box or ::after's 6px inset misaligns */
    position: absolute;
    inset: 0;
    border-radius: 50%;
    border: 5px solid rgba(255, 255, 255, 0.65);
    /* Negative delay starts the sweep mid-draw, so a ring is on screen from the first
       frame instead of blank if the main thread stalls on mount. */
    animation: p1-loader-clip 2s linear -1s infinite;
}

/* Inner ring: same sweep, counter-rotating at twice the speed. Takes the BRAND accent, not the raw
   P1 red — the loader is the first thing a client sees, and on a Skynexe build it was red. */
.p1-loader::after {
    border-color: var(--p1-brand-accent);
    animation: p1-loader-clip 2s linear -1s infinite,
               p1-loader-rotate 0.5s linear infinite reverse;
    inset: 6px;
}

.p1-loader--light::before {
    border-color: var(--p1-border-light);
}

.p1-loader--light::after {
    border-color: var(--p1-brand-accent);
}

@media (prefers-reduced-motion: reduce) {
    .p1-loader,
    .p1-loader::before,
    .p1-loader::after {
        animation-duration: 5s, 5s;
    }
}

.p1-loader-backdrop {
    display: flex;
    align-items: center;
    justify-content: center;
    height: 100%;
    background: rgba(10, 10, 11, 0.72);
}

.p1-loader-backdrop--overlay {
    position: absolute;
    inset: 0;
    z-index: 10;
    height: auto;
}

.p1-loader-backdrop--solid {
    background: rgba(10, 10, 11, 0.96);
}

/* Fully opaque: the point of the light variant is that partial content cannot show through. */
.p1-loader-backdrop--light {
    background: #ffffff;
}

.p1-loader-stack {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: 14px;
}

/* P1 mark above the "Loading" label. The .logo-dark/.logo-light pair is swapped by the
   theme rules above — the pre-boot script sets data-theme before this ever paints. */
.p1-loader-logo {
    height: 34px;
    object-fit: contain;
    margin-bottom: 2px;
}

.p1-loader-label {
    font-size: var(--p1-text-2xs);
    font-weight: var(--p1-weight-semibold);
    /* Was 2.5px — the only px tracking in the app, and at 10px that's 0.25em: five times wider
       than every other uppercase label. */
    letter-spacing: var(--p1-tracking-caps);
    text-transform: uppercase;
    color: var(--p1-text-muted);
}

.p1-loader-backdrop--light .p1-loader-label {
    color: var(--p1-text-muted-light);
}

/* Pre-boot splash only: #app is unsized, so the backdrop's height: 100% has nothing to resolve against.
   Background is tokenised so the splash follows the theme instead of always being black. */
.p1-loader-splash {
    min-height: 100vh;
    background: var(--p1-bg);
}

.p1-loader-splash .p1-loader-label {
    color: var(--p1-text-muted);
}

/* In light mode the outer ring must be dark, or it disappears against the white splash. */
html[data-theme="light"] .p1-loader-splash .p1-loader::before {
    border-color: var(--p1-border-light);
}

/* ── Nav styles (global — Blazor CSS isolation can't reach the <a> that NavLink renders,
      so the base layout rules must live here alongside the .active variants) ── */
/* Mobile bottom tab bar */
/* Scoped under .shell-bottom-nav so it comfortably outranks the generic `a { color: inherit }`
   reset, which otherwise pulls the tabs back to the body's white text colour. */
.shell-bottom-nav .shell-tab-btn,
.shell-tab-btn {
    display: flex;
    flex: 1;                 /* equal-width columns, so each icon+label centres in its own space */
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 7px;                /* was 3px — more air between the icon and its label */
    padding: 4px 0;
    /* Muted, not white. Tokenised so it stays legible in light mode too. */
    color: var(--p1-text-muted);
    text-decoration: none;
    font-size: var(--p1-text-2xs);
    font-weight: 500;
    border-radius: 12px;
    transition: color 0.15s;
}
.shell-tab-btn .p1-icon {
    stroke-width: 1.8;
}
.shell-bottom-nav .shell-tab-btn.active,
.shell-tab-btn.active {
    color: var(--p1-brand-accent) !important;
}
.shell-tab-btn.active .p1-icon {
    stroke-width: 2.5;
}
/* Desktop sidebar icon buttons */
.shell-nav-btn {
    width: 40px;
    height: 40px;
    display: flex;
    align-items: center;
    justify-content: center;
    border-radius: 12px;
    color: var(--p1-text-muted);
    text-decoration: none;
    transition: background 0.15s, color 0.15s;
    position: relative;
}
.shell-nav-btn:hover {
    background: var(--p1-surface-inner);
    color: var(--p1-text-primary);
}
.shell-nav-btn.active {
    background: rgba(var(--p1-brand-accent-rgb), 0.14) !important;
    color: var(--p1-brand-accent) !important;
}
/* The rail's active indicator — brand accent, not a literal red. */
.shell-nav-btn.active::before {
    content: "";
    position: absolute;
    left: -8px;
    top: 8px;
    bottom: 8px;
    width: 3px;
    background: var(--p1-brand-accent);
    border-radius: 0 2px 2px 0;
}

/* ── Setup flow typographic reset (global) ──
   The setup pages set explicit spacing on wrappers (e.g. __header { margin-bottom }) but the
   <p> subtitles/hints and <h2> titles still carried the browser's default ~1em margins, which
   inflated every header, hint and card gap. Reset them here. Page-scoped rules that DO declare a
   margin still win (CSS isolation appends [b-hash], giving them higher specificity than this). */
.p1-setup-welcome p,      .p1-setup-welcome h1,      .p1-setup-welcome h2,      .p1-setup-welcome h3,
.p1-setup-personal p,     .p1-setup-personal h1,     .p1-setup-personal h2,     .p1-setup-personal h3,
.p1-setup-suitability p,  .p1-setup-suitability h1,  .p1-setup-suitability h2,  .p1-setup-suitability h3,
.p1-setup-declarations p, .p1-setup-declarations h1, .p1-setup-declarations h2, .p1-setup-declarations h3,
.p1-setup-bank p,         .p1-setup-bank h1,         .p1-setup-bank h2,         .p1-setup-bank h3,
.p1-setup-complete p,     .p1-setup-complete h1,     .p1-setup-complete h2,     .p1-setup-complete h3 {
    margin-top: 0;
    margin-bottom: 0;
}

/* ── Theme-aware logo (global) ──
   Two <img> tags; CSS picks the right one off <html data-theme>. Selectors are deliberately
   specific (html[data-theme] img.logo-*) because the scoped .shell-logo-full rule loads after
   this file and would otherwise win on a specificity tie.
   mix-blend-mode: screen is what makes the WHITE logo sit on the dark background — applied to a
   BLACK logo it would erase it entirely, so the light variant resets it. */
.logo-light {
    display: none;
}

html[data-theme="light"] img.logo-dark {
    display: none;
}

html[data-theme="light"] img.logo-light {
    display: block;
    mix-blend-mode: normal;
}

/* ── Shared page title (global) ──
   Used by Activity, Notifications, etc. It was only defined in Activity's scoped CSS, so
   Notifications' <h1 class="p1-title"> fell back to the browser default (larger, stray margins)
   and didn't match the other pages. Global so every page header is the same size. */
.p1-title {
    font-size: var(--p1-text-3xl);
    font-weight: 700;
    color: var(--p1-text-primary);
    margin: 0 0 1rem;
    padding: 0;
}

/* ── Layout utilities (global) ──
   The pages were ported from the React/Tailwind designs and still use Tailwind-style class
   names (flex, gap-2, mt-3, text-xs …), but Tailwind is not part of this app — so every one of
   them was a no-op. That is why chip rows stacked vertically and vertical rhythm was missing.
   This is the minimal layer covering the classes actually used; it follows Tailwind's 0.25rem
   scale so the markup ported from the designs behaves as designed. */
.flex        { display: flex; }
.inline-flex { display: inline-flex; }
.flex-1      { flex: 1 1 0%; }
.flex-col    { flex-direction: column; }
.flex-wrap   { flex-wrap: wrap; }

.items-center    { align-items: center; }
.items-start     { align-items: flex-start; }
.justify-between { justify-content: space-between; }
.justify-center  { justify-content: center; }
.justify-end     { justify-content: flex-end; }

.gap-1 { gap: 0.25rem; }
.gap-2 { gap: 0.5rem; }
.gap-3 { gap: 0.75rem; }
.gap-4 { gap: 1rem; }

.mt-0 { margin-top: 0; }
.mt-1 { margin-top: 0.25rem; }
.mt-2 { margin-top: 0.5rem; }
.mt-3 { margin-top: 0.75rem; }
.mt-4 { margin-top: 1rem; }
.mt-5 { margin-top: 1.25rem; }
.mt-6 { margin-top: 1.5rem; }

.mb-1 { margin-bottom: 0.25rem; }
.mb-2 { margin-bottom: 0.5rem; }
.mb-3 { margin-bottom: 0.75rem; }
.mb-4 { margin-bottom: 1rem; }
.mb-6 { margin-bottom: 1.5rem; }

/* Sizes come from the scale in tokens.css. Note there is no Tailwind here — if a class isn't in
   this file it does nothing at all, silently. `text-[10px]` was used in the markup and rendered
   nothing, because arbitrary-value syntax needs a compiler this app doesn't have. */
.text-2xs    { font-size: var(--p1-text-2xs); }
.text-xs     { font-size: var(--p1-text-sm); }   /* 0.75rem — name kept, it's used in markup */
.text-sm     { font-size: var(--p1-text-base); } /* 0.875rem — ditto */
.text-right  { text-align: right; }
.text-center { text-align: center; }
.uppercase   { text-transform: uppercase; }

.font-normal   { font-weight: 400; }
.font-medium   { font-weight: var(--p1-weight-medium); }
.font-semibold { font-weight: var(--p1-weight-semibold); }
.font-bold     { font-weight: var(--p1-weight-bold); }

/* Text tones. `.text-muted` existed only as a SCOPED rule inside Activity.razor.css, so it was one
   copy-paste away from silently doing nothing anywhere else. `.text-white` was used in the markup
   and defined nowhere at all. */
.text-primary   { color: var(--p1-text-primary); }
.text-secondary { color: var(--p1-text-secondary); }
.text-muted     { color: var(--p1-text-muted); }

/* ── The uppercase eyebrow ──
   Rebuilt 18 times across the app, no two alike: sizes 10px/11px/12px, weights 500/600/700,
   tracking 0.05/0.06/0.1em. Two roles were doing real work, so there are two classes, and they
   agree on everything except size. */
.p1-eyebrow {
    font-size: var(--p1-text-xs);
    font-weight: var(--p1-weight-bold);
    text-transform: uppercase;
    letter-spacing: var(--p1-tracking-caps);
    color: var(--p1-text-muted);
}

/* The heading above a group of rows — a day in a timeline, a section of a list. */
.p1-group-heading {
    margin: 0 0 0.5rem 0.25rem;
    font-size: var(--p1-text-sm);
    font-weight: var(--p1-weight-semibold);
    text-transform: uppercase;
    letter-spacing: var(--p1-tracking-caps);
    color: var(--p1-text-secondary);
}

.w-full   { width: 100%; }
.min-w-0  { min-width: 0; }
.shrink-0 { flex-shrink: 0; }
.relative { position: relative; }
.truncate {
    overflow: hidden;
    text-overflow: ellipsis;
    white-space: nowrap;
}

/* ── Responsive visibility utilities ── */
.mobile-only  { display: block; }
.desktop-only { display: none !important; }

@media (min-width: 768px) {
    .mobile-only  { display: none !important; }
    .desktop-only { display: block !important; }
}

/* ── "See all" link, reinforced ──
   The rule lives in components.css, but app.css loads after it and contains a global
   `a, a:visited { color: inherit }` reset. This higher-specificity selector (a.see-all)
   guarantees the link is red regardless of load order or future resets. */
a.see-all,
a.see-all:visited {
    color: var(--p1-brand-accent);
    display: inline-flex;
    align-items: center;
    gap: 0.3rem;
    font-size: var(--p1-text-md);
    font-weight: 600;
    text-decoration: none;
    white-space: nowrap;
}

/* ── Avatar initials stay white in BOTH themes ──
   The initials sit on a solid red/purple fill, not on the page, so they must not follow the theme's
   text colour. This is stated explicitly (rather than relying on the component's own #ffffff)
   because a token sweep has already once converted that literal and turned the initials dark on
   red. .avatar--neutral is the exception — it has no fill, so it keeps the theme's text colour. */
html[data-theme="light"] .shell-avatar,
html[data-theme="light"] .avatar:not(.avatar--neutral) {
    color: var(--p1-text-on-accent);
}


/* ── Embedded in the /preview device frame ──
   Hide scrollbars so the phone reads as a phone. Scoped to [data-embedded], which the pre-boot
   script sets only when the app is in an iframe — the real app keeps its scrollbars. */
html[data-embedded],
html[data-embedded] body,
html[data-embedded] * {
    scrollbar-width: none;          /* Firefox */
    -ms-overflow-style: none;       /* legacy Edge */
}

html[data-embedded] ::-webkit-scrollbar {
    width: 0;
    height: 0;
    display: none;
}

/* ── Branded navigation ──
   A reseller's nav bar is a fixed colour in BOTH themes, so anything sitting on it must stop
   following the theme — in light mode the theme's text colour would be near-black on a dark navy
   bar, and the logo's screen blend would eat it. */
/* Both branded nav surfaces are a fixed colour in BOTH themes, so their inactive icons must stop
   following the theme — in light mode the theme's text colour would be near-black on navy. */
html[data-brand] .shell-nav-btn,
html[data-brand] .shell-bottom-nav .shell-tab-btn {
    color: var(--p1-nav-fg) !important;
}

/* Active still wins: the brand accent (#0097d6) against the brand primary (#192838). */
html[data-brand] .shell-bottom-nav .shell-tab-btn.active {
    color: var(--p1-brand-accent) !important;
}

html[data-brand] .shell-nav-btn:hover {
    background: rgba(255, 255, 255, 0.08);
    color: var(--p1-text-on-accent);
}

/* mix-blend-mode: screen makes the WHITE P1 mark sit on the dark page. On a branded nav bar the
   logo is supplied to suit that bar, so the blend has to come off or it would wash out. */
html[data-brand] .shell-logo-full,
html[data-brand] .shell-icon-mark {
    mix-blend-mode: normal;
}

/* NOTE: no data-brand override here. A brand with no light variant renders a SINGLE <img> with no
   .logo-dark/.logo-light class at all (see MainLayout), so it is unaffected by the theme swap —
   forcing .logo-dark visible in light mode only broke the loader, which is static P1 markup. */


/* ── Desktop rail tooltips ─────────────────────────────────────────────────────
   The sidebar is icons only. Each item names itself on hover and on keyboard focus. */
.shell-nav-item {
    position: relative;
    display: flex;
}

.shell-nav-tip {
    position: absolute;
    left: calc(100% + 0.75rem);
    top: 50%;
    transform: translateY(-50%) translateX(-0.25rem);

    padding: 0.4375rem 0.75rem;
    border-radius: 0.625rem;
    /* The brand accent — P1 red, Skynexe blue — with white on top. */
    background: var(--p1-brand-accent);
    border: 1px solid var(--p1-brand-accent);
    box-shadow: 0 8px 20px rgba(0, 0, 0, 0.28);

    font-size: var(--p1-text-md);
    font-weight: 600;
    line-height: 1;
    color: var(--p1-text-on-accent);
    white-space: nowrap;

    opacity: 0;
    pointer-events: none;
    transition: opacity 0.14s ease, transform 0.14s ease;
    z-index: 60;
}

.shell-nav-item:hover .shell-nav-tip,
.shell-nav-btn:focus-visible ~ .shell-nav-tip {
    opacity: 1;
    transform: translateY(-50%) translateX(0);
}
