/* ============================================================================
   Theme — "Scandinavian roastery" on a Bootstrap skeleton.
   A palette of custom properties feeds a single shared block that maps them
   onto Bootstrap 5.3's CSS variables, so existing utility classes (btn-primary,
   card, text-muted, bg-light, badges…) pick up the look without markup edits.
   ========================================================================== */

:root {
    --app-bg: #F6F1E7;
    --surface: #FFFFFF;
    --surface-2: #F1EADB;          /* bg-light panels */
    --surface-2-rgb: 241, 234, 219;
    --ink: #2B211A;
    --ink-rgb: 43, 33, 26;
    /* Dark enough for WCAG AA (4.5:1) as body text on both the cream page bg and white
       cards — text-muted carries real copy here (hero lead, form hints), not decoration. */
    --muted: #6B5D4F;
    --muted-rgb: 107, 93, 79;
    --line: #E7DECF;
    --line-strong: #D8CCB8;
    --accent: #C2613E;
    --accent-rgb: 194, 97, 62;
    --accent-hover: #A9512F;
    --accent-hover-rgb: 169, 81, 47;
    --accent-active: #934628;
    --accent-contrast: #FFFFFF;
    --success-rgb: 111, 125, 79;   /* muted olive */
    --info-rgb: 78, 127, 123;      /* muted teal */
    /* Premium yellow (badges, premium buttons, locks) — Bootstrap's stock warning #ffc107,
       kept deliberately: the premium buttons launched in it, so it IS the premium color. */
    --warning-rgb: 255, 193, 7;
    --warning-hover: #FFCA2C;
    --warning-active: #FFCD39;
    --chip-secondary-rgb: 138, 124, 110;

    --radius: 0.75rem;
    --radius-sm: 0.5rem;
    --font-display: "Fraunces", Georgia, serif;
    --font-body: "Inter", "Helvetica Neue", Helvetica, Arial, sans-serif;
    --font-data: "JetBrains Mono", Consolas, monospace;

    --card-border: transparent;
    --card-shadow: 0 1px 2px rgba(43, 33, 26, .05), 0 6px 20px rgba(43, 33, 26, .07);
    --card-shadow-hover: 0 2px 4px rgba(43, 33, 26, .06), 0 14px 32px rgba(43, 33, 26, .12);

    --topbar-bg: rgba(246, 241, 231, .92);
    --sidebar-bg: #2B211A;
    --sidebar-line: rgba(242, 233, 220, .1);
    --sidebar-fg: #F2E9DC;
    --sidebar-muted: #B8A795;
    --sidebar-hover: rgba(242, 233, 220, .08);
    --sidebar-active-bg: rgba(194, 97, 62, .3);
    --sidebar-active-fg: #F4BFA4;

    --bt-line: #D6C9B3;            /* BrewTimeline connectors */
}

/* ---- Map the palette onto Bootstrap's CSS variables ----------------------- */
:root {
    --bs-body-bg: var(--app-bg);
    --bs-body-color: var(--ink);
    --bs-body-color-rgb: var(--ink-rgb);
    --bs-emphasis-color: var(--ink);
    --bs-emphasis-color-rgb: var(--ink-rgb);
    --bs-secondary-color: var(--muted);          /* text-muted */
    --bs-secondary-color-rgb: var(--muted-rgb);
    --bs-tertiary-color: var(--muted);
    --bs-secondary-bg: var(--surface-2);
    --bs-tertiary-bg: var(--surface-2);
    --bs-border-color: var(--line);
    --bs-link-color: var(--accent);
    --bs-link-color-rgb: var(--accent-rgb);
    --bs-link-hover-color: var(--accent-hover);
    --bs-link-hover-color-rgb: var(--accent-hover-rgb);
    --bs-primary: var(--accent);
    --bs-primary-rgb: var(--accent-rgb);
    --bs-light-rgb: var(--surface-2-rgb);        /* bg-light panels */
    --bs-secondary-rgb: var(--chip-secondary-rgb);
    --bs-success-rgb: var(--success-rgb);
    --bs-success: rgb(var(--success-rgb));
    --bs-info-rgb: var(--info-rgb);
    --bs-info: rgb(var(--info-rgb));
    --bs-warning-rgb: var(--warning-rgb);
    --bs-warning: rgb(var(--warning-rgb));
    --bs-border-radius: var(--radius-sm);
    --bs-border-radius-sm: calc(var(--radius-sm) - 0.125rem);
    --bs-border-radius-lg: var(--radius);
    --bs-body-font-family: var(--font-body);
    --bs-focus-ring-color: rgba(var(--accent-rgb), .25);
}

html, body {
    font-family: var(--font-body);
}

/* Scroll anchors (the intake read-back's Coffee Journey nudge; the premium page's "Get Premium"
   jump to the Get Premium box) stop clear of the sticky topnav rather than tucking under it. */
#coffee-journey,
#plans,
#get-premium,
#autopilot {
    scroll-margin-top: 66px;
}

/* Every heading on /how-it-works is a deep-link target, so the rule is on the page
   rather than on an id list that would have to grow with each new question. */
.hiw-page h2,
.hiw-page h3 {
    scroll-margin-top: 66px;
}

/* ---- Typography ----------------------------------------------------------- */
h1, h2, h3, .h1, .h2, .h3 {
    font-family: var(--font-display);
    font-weight: 600;
    letter-spacing: -0.01em;
}

h1, .h1 {
    margin-bottom: 0.5rem;
}

h1, .h1 {
    font-size: 2.6rem;
}

/* Legal pages (/terms, /privacy): long-form prose, so calmer heading sizes and
   roomier line height than the app screens. */
.legal-page h2 {
    font-size: 1.5rem;
    margin-top: 2.25rem;
}

.legal-page h3 {
    font-size: 1.15rem;
    margin-top: 1.5rem;
}

.legal-page p,
.legal-page li {
    line-height: 1.65;
}

/* /how-it-works section jump list. Wraps into a column on phones and sits inline on
   wider screens; deliberately plain links rather than a card, so it reads as part of
   the prose instead of competing with the first question. */
.hiw-toc {
    display: flex;
    flex-direction: column;
    gap: 2px;
    margin: 1.5rem 0 2.5rem;
    padding: 1rem 1.1rem;
    border-left: 2px solid var(--accent);
    background: var(--surface-2);
    border-radius: 0 8px 8px 0;
}

.hiw-toc a {
    padding: 8px 0;   /* 44px rows once line-height is counted (touch targets) */
    font-size: 0.95rem;
    text-decoration: none;
}

.hiw-toc a:hover,
.hiw-toc a:focus-visible {
    text-decoration: underline;
}

/* Section separation. Each h2 opens a new band of the page, so it takes a rule and a lot of
   air above it, while the questions inside a section sit closer together than the sections
   are to each other. The grouping then reads without needing a card or a background. */
.hiw-page h2 {
    margin-top: 4rem;
    padding-top: 2rem;
    border-top: 2px solid var(--line);
    font-size: 1.7rem;
}

.hiw-page h3 {
    margin-top: 2.25rem;
}

/* Per-heading "#" permalink, sitting in front of the heading text. Always rendered rather
   than hover-revealed: there is no hover on a phone, and a copyable deep link is the whole
   point. Muted until interacted with so it sits behind the heading text. */
.hiw-anchor {
    padding: .2em .4em .2em .1em;
    font-family: var(--font-data);
    font-weight: 400;
    color: var(--muted);
    opacity: .45;
    text-decoration: none;
}

.hiw-anchor:hover,
.hiw-anchor:focus-visible {
    color: var(--accent);
    opacity: 1;
    text-decoration: none;
}

.hiw-outro {
    margin-top: 3rem;
    padding-top: 1.25rem;
    border-top: 1px solid var(--line);
    color: var(--muted);
}

/* Mono temperatures on the brew steps read like an instrument, while the rest
   of the page stays editorial. */
.bt-step {
    font-family: var(--font-data);
    font-size: 0.95em;
}

/* ---- Links & buttons ------------------------------------------------------ */
a, .btn-link {
    color: var(--accent);
}

.btn-primary {
    --bs-btn-color: var(--accent-contrast);
    --bs-btn-bg: var(--accent);
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-color: var(--accent-contrast);
    --bs-btn-hover-bg: var(--accent-hover);
    --bs-btn-hover-border-color: var(--accent-hover);
    --bs-btn-active-color: var(--accent-contrast);
    --bs-btn-active-bg: var(--accent-active);
    --bs-btn-active-border-color: var(--accent-active);
    --bs-btn-disabled-color: var(--accent-contrast);
    --bs-btn-disabled-bg: var(--accent);
    --bs-btn-disabled-border-color: var(--accent);
    --bs-btn-focus-shadow-rgb: var(--accent-rgb);
}

.btn-outline-primary {
    --bs-btn-color: var(--accent);
    --bs-btn-border-color: var(--accent);
    --bs-btn-hover-color: var(--accent-contrast);
    --bs-btn-hover-bg: var(--accent);
    --bs-btn-hover-border-color: var(--accent);
    --bs-btn-active-color: var(--accent-contrast);
    --bs-btn-active-bg: var(--accent);
    --bs-btn-active-border-color: var(--accent);
    --bs-btn-disabled-color: var(--accent);
    --bs-btn-disabled-border-color: var(--accent);
    --bs-btn-focus-shadow-rgb: var(--accent-rgb);
}

.btn-outline-secondary {
    --bs-btn-color: var(--muted);
    --bs-btn-border-color: var(--line-strong);
    --bs-btn-hover-color: var(--ink);
    --bs-btn-hover-bg: var(--surface-2);
    --bs-btn-hover-border-color: var(--line-strong);
    --bs-btn-active-color: var(--ink);
    --bs-btn-active-bg: var(--surface-2);
    --bs-btn-active-border-color: var(--line-strong);
    --bs-btn-disabled-color: var(--muted);
    --bs-btn-disabled-border-color: var(--line);
}

/* Premium's gold. Bootstrap compiles .btn-warning's colors in (stock #ffc107), so mapping
   --bs-warning above only reaches badges/text — restate the button variables so premium
   buttons render the same palette gold as the premium badges. */
.btn-warning {
    --bs-btn-color: var(--ink);
    --bs-btn-bg: rgb(var(--warning-rgb));
    --bs-btn-border-color: rgb(var(--warning-rgb));
    --bs-btn-hover-color: var(--ink);
    --bs-btn-hover-bg: var(--warning-hover);
    --bs-btn-hover-border-color: var(--warning-hover);
    --bs-btn-active-color: var(--ink);
    --bs-btn-active-bg: var(--warning-active);
    --bs-btn-active-border-color: var(--warning-active);
    --bs-btn-disabled-color: var(--ink);
    --bs-btn-disabled-bg: rgb(var(--warning-rgb));
    --bs-btn-disabled-border-color: rgb(var(--warning-rgb));
    --bs-btn-focus-shadow-rgb: var(--warning-rgb);
}

.btn-light {
    --bs-btn-color: var(--ink);
    --bs-btn-bg: var(--surface-2);
    --bs-btn-border-color: var(--surface-2);
    --bs-btn-hover-color: var(--ink);
    --bs-btn-hover-bg: var(--line);
    --bs-btn-hover-border-color: var(--line);
    --bs-btn-active-color: var(--ink);
    --bs-btn-active-bg: var(--line);
    --bs-btn-active-border-color: var(--line);
    --bs-btn-disabled-color: var(--muted);
    --bs-btn-disabled-bg: var(--surface-2);
    --bs-btn-disabled-border-color: var(--surface-2);
}

/* ---- Cards ----------------------------------------------------------------- */
.card {
    --bs-card-bg: var(--surface);
    --bs-card-border-color: var(--card-border);
    --bs-card-border-radius: var(--radius);
    --bs-card-inner-border-radius: calc(var(--radius) - 1px);
    --bs-card-cap-bg: transparent;
    box-shadow: var(--card-shadow);
}

a.card {
    transition: transform .15s ease, box-shadow .15s ease, border-color .15s ease;
}

a.card:hover {
    transform: translateY(-3px);
    box-shadow: var(--card-shadow-hover);
}

/* ---- Forms ------------------------------------------------------------------ */
.form-control, .form-select {
    background-color: var(--surface);
    border-color: var(--line-strong);
    color: var(--ink);
}

.form-control:focus, .form-select:focus {
    background-color: var(--surface);
    border-color: var(--accent);
    color: var(--ink);
    box-shadow: 0 0 0 0.25rem rgba(var(--accent-rgb), .15);
}

.form-check-input:checked {
    background-color: var(--accent);
    border-color: var(--accent);
}

.form-check-input:focus,
.btn:focus-visible {
    border-color: var(--accent);
    box-shadow: 0 0 0 0.25rem rgba(var(--accent-rgb), .2);
}

.input-group-text {
    background-color: var(--surface-2);
    border-color: var(--line-strong);
    color: var(--muted);
}

/* The premium cup mark (PremiumCup component) aligns like a Bootstrap icon. */
.premium-cup {
    vertical-align: -0.125em;
    flex-shrink: 0;
}

/* A crossed-out reference price (the /premium plan cards) that stays readable: the
   browser-default line-through is as heavy as the digits' strokes, so thin it out and
   fade it — clearly struck, still legible. */
.price-was {
    color: rgb(var(--muted-rgb));
    text-decoration-line: line-through;
    text-decoration-thickness: 0.07em;
    text-decoration-color: rgba(var(--muted-rgb), 0.55);
}

/* ---- Badges ------------------------------------------------------------------ */
/* Our info chips are darker than stock Bootstrap cyan, so flip its black text. */
.text-bg-info {
    color: #fff !important;
}

/* ---- Eyebrow micro-label ------------------------------------------------------ */
/* The landing page's uppercase label above headings, shared site-wide so page headers
   read like the landing's sections. */
.ed-eyebrow {
    display: block;
    font-size: 11px;
    font-weight: 600;
    letter-spacing: .12em;
    text-transform: uppercase;
    color: var(--muted);
    margin-bottom: 12px;
}

/* ---- Sign-in wall (guests on signed-in pages; SignInWall.razor) ---------------- */
.signin-wall {
    max-width: 560px;
    margin: 48px auto;
    padding: 0 16px;
    text-align: center;
}

.signin-wall p {
    margin: 10px 0 24px;
}

/* ---- Hero feature icons ------------------------------------------------------- */
/* Line-art icons that mirror the site logo (login-logo.svg): the accent stroke,
   round caps/joins, sat on the light hero panel. Sized to the old emoji footprint. */
/* Single-column grid for the feature bullets. On the desktop two-column hero,
   equal-height rows (grid-auto-rows: 1fr) put the icons on an even vertical
   rhythm regardless of how long each description is. */
.hero-features {
    display: grid;
    gap: 1rem;
}

@media (min-width: 992px) {
    .hero-features {
        grid-auto-rows: 1fr;
    }
}

/* The landing hero's premium feature is a link dressed as its sibling cards; only the
   badge and a hover tint give it away. */
.hero-feature-link {
    text-decoration: none;
    color: inherit;
}

.hero-feature-link:hover .fw-semibold {
    color: var(--accent-hover);
}

.hero-icon {
    display: inline-flex;
    flex: 0 0 auto;
    color: var(--accent);
}

.hero-icon svg {
    width: 1.6rem;
    height: 1.6rem;
}

/* ---- Autopilot hero (My bags) ---------------------------------------------------
   The premium pitch: copy left, an illustrative freshness-curve chart right. All colors
   come from the palette; the chart keeps grid/labels recessive (muted/line) and reserves
   the accent for the data marks. */
.autopilot-hero,
.journey-hero {
    background: linear-gradient(135deg, var(--surface) 55%, var(--surface-2));
}

.autopilot-hero .ap-title,
.journey-hero .ap-title {
    font-family: var(--font-display);
}

/* The illustrative Coffee Journey read on the premium pitch: a real-looking branded card sitting
   on the hero's gradient. */
.journey-hero-sample {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: var(--radius);
    box-shadow: var(--card-shadow);
    padding: 1.1rem 1.2rem;
    max-width: 440px;
    margin-inline: auto;
}

.ap-points li {
    display: flex;
    gap: .6rem;
    margin-bottom: .5rem;
}

.ap-points i {
    color: var(--accent);
    font-size: 1.1rem;
    line-height: 1.4;
}

.ap-chart-wrap {
    /* The viewBox is 560×232; without a cap the SVG (text included) scales with the
       column, which is comically large on wide screens. 620px ≈ its natural size.
       Relative so the infobox can position by viewBox proportions. */
    position: relative;
    width: 100%;
    max-width: 620px;
    margin-inline: auto;
}

.ap-chart {
    width: 100%;
    height: auto;
    display: block;
}

/* Oversized invisible hit target over each push dot. */
.ap-hit {
    fill: transparent;
    cursor: pointer;
    outline: none;
}

.ap-hit:focus-visible + .ap-dot,
.ap-hit:focus-visible {
    stroke: rgba(var(--accent-rgb), .4);
    stroke-width: 2;
}

/* The push infobox: anchored at the dot's position, floated above it (below for
   dots near the top edge). pointer-events off so it never steals the hover. */
.ap-tip {
    position: absolute;
    transform: translate(-50%, -118%);
    width: max-content;
    max-width: 240px;
    background: var(--surface);
    border: 1px solid var(--line-strong);
    border-radius: var(--radius-sm);
    box-shadow: var(--card-shadow);
    padding: .5rem .65rem;
    font-size: .8rem;
    line-height: 1.35;
    pointer-events: none;
    z-index: 5;
}

.ap-tip-below {
    transform: translate(-62%, 14%);
}

.ap-band-fresh { fill: rgba(var(--accent-rgb), .06); }
.ap-band-resting { fill: rgba(var(--warning-rgb), .07); }
.ap-band-rested { fill: rgba(var(--warning-rgb), .14); }

.ap-phase {
    font: 600 9px var(--font-body);
    fill: var(--muted);
    letter-spacing: .08em;
}

.ap-grid { stroke: var(--line); stroke-width: 1; }
.ap-tick { font: 10px var(--font-data); fill: var(--muted); }
.ap-axis { font: 11px var(--font-body); fill: var(--muted); }

.ap-line {
    fill: none;
    stroke: var(--accent);
    stroke-width: 2.5;
    stroke-linejoin: round;
}

.ap-leader { stroke: var(--line-strong); stroke-width: 1; }
.ap-event { font: 600 11px var(--font-body); fill: var(--ink); }

.ap-dot {
    fill: var(--accent);
    stroke: var(--surface);
    stroke-width: 2;
}

/* ---- Bag freshness timeline (BagFreshnessTimeline, My bags page) ----------------- */
/* Zone tints echo the hero chart's phase bands (accent = fresh, warning = resting/
   rested) at bar strength, with the olive success tone marking the peak window. */
.bag-timeline {
    max-width: 560px;
    margin-top: .6rem;
}

/* The expanded bag card's control column: stacked labelled fields + the action group share
   the timeline's cap, so phones get true full-width inputs while desktop stays sane. */
.bag-controls {
    max-width: 560px;
}

.bag-timeline-track {
    position: relative;
    display: flex;
    height: 10px;
    border-radius: 999px;
}

/* The three phases of docs/freshness-rules.md §7.4 — never a fourth zone. The rounded ends and
   the tired zone's flex key off the zone classes, NOT :first-child/:last-child: the position
   marker is the track's real last child, so a :last-child rule matches nothing and silently
   collapses the tired zone (which carries no explicit width) to nothing. */
.bag-timeline-zone-resting {
    background: rgba(var(--accent-rgb), .3);
    border-radius: 999px 0 0 999px;
}

.bag-timeline-zone-ready { background: rgba(var(--success-rgb), .38); }

/* Takes the rest of the track — the tail past the tired day, so an old bag's marker has
   somewhere to sit (BagFreshnessTimeline.Total). */
.bag-timeline-zone-tired {
    background: rgba(var(--warning-rgb), .5);
    border-radius: 0 999px 999px 0;
    flex: 1;
}

.bag-timeline-marker {
    position: absolute;
    top: 50%;
    width: 16px;
    height: 16px;
    border-radius: 50%;
    background: var(--accent);
    border: 2px solid var(--surface);
    box-shadow: 0 1px 3px rgba(var(--ink-rgb), .35);
    transform: translate(-50%, -50%);
}

/* Frozen: the clock is paused, so the marker reads as chilled rather than "live" — a cool
   fill and a ring so it stands apart from the moving accent dot. */
.bag-timeline-marker-frozen {
    background: #7fb8e6;
    box-shadow: 0 0 0 3px rgba(127, 184, 230, .35), 0 1px 3px rgba(var(--ink-rgb), .35);
}

.bag-timeline-labels {
    display: flex;
    margin-top: .3rem;
}

.bag-timeline-labels span {
    font: 600 .62rem var(--font-body);
    letter-spacing: .06em;
    text-transform: uppercase;
    color: var(--muted);
    text-align: center;
    white-space: nowrap;
    overflow: hidden;
}

.bag-timeline-labels span:last-child { flex: 1; }

/* The free-tier face: zones visible but visibly inert, position withheld. */
.bag-timeline-locked .bag-timeline-track,
.bag-timeline-locked .bag-timeline-labels {
    filter: grayscale(1);
    opacity: .5;
}

.bag-timeline-lock {
    display: inline-block;
    margin-top: .15rem;
    font-size: .78rem;
    color: var(--muted);
    text-decoration: none;
}

.bag-timeline-lock:hover {
    color: var(--accent-hover);
    text-decoration: underline;
}

/* The lock itself carries the premium gold (text stays muted for contrast). */
.bag-timeline-lock .bi {
    color: rgb(var(--warning-rgb));
}

/* ---- Personal note on a bag (My bags) --------------------------------------------- */
/* A jotted note reads like a sticky slip: a faint panel with an accent edge, small muted
   text, and the user's own line breaks kept. */
.bag-note {
    font-size: .875rem;
    color: var(--muted);
    background: rgba(var(--surface-2-rgb), .6);
    border-left: 3px solid var(--accent);
    border-radius: .25rem;
    padding: .4rem .6rem;
}

.bag-note > .bi-sticky {
    color: var(--accent);
    flex: none;
    margin-top: .1rem;
}

.bag-note-text {
    white-space: pre-wrap;
    overflow-wrap: anywhere;
}

/* On a collapsed bag row the note stays to one compact block: the sticky icon sits beside the
   text, which clamps to two lines with an ellipsis (the full note is in the title tooltip and on
   expand). */
.bag-note-collapsed {
    display: flex;
    align-items: flex-start;
    gap: .35rem;
    margin-top: .15rem;
}

.bag-note-collapsed > .bi-sticky {
    color: var(--accent);
    flex: none;
    margin-top: .1rem;
}

.bag-note-collapsed > span {
    display: -webkit-box;
    -webkit-line-clamp: 2;
    line-clamp: 2;
    -webkit-box-orient: vertical;
    overflow: hidden;
    overflow-wrap: anywhere;
}

/* ---- Frozen bag card (My bags) ---------------------------------------------------- */
/* A frozen bag reads cold at a glance: rime frost creeps in from every corner over an
   icy-blue base, with a big snowflake in the top corner. Content is lifted above the frost
   with z-index. */
.bag-card-frozen {
    position: relative;
    overflow: hidden;
    background:
        radial-gradient(130px 130px at 0% 0%, rgba(255, 255, 255, .95), transparent 70%),
        radial-gradient(150px 150px at 100% 0%, rgba(214, 239, 255, .92), transparent 70%),
        radial-gradient(170px 170px at 100% 100%, rgba(255, 255, 255, .88), transparent 72%),
        radial-gradient(150px 150px at 0% 100%, rgba(219, 234, 254, .88), transparent 70%),
        linear-gradient(#eef7ff, #eef7ff);
    border-color: rgba(147, 197, 253, .65);
    box-shadow: 0 2px 14px rgba(147, 197, 253, .3);
    color: #0f2942;
}

/* Big corner watermark flakes (from the "deep freeze" look): one top-right (::before), one
   bottom-left (::after), both clipped by the card's overflow. */
.bag-card-frozen::before,
.bag-card-frozen::after {
    content: "\2744";
    position: absolute;
    line-height: 1;
    color: rgba(255, 255, 255, .6);
    text-shadow: 0 2px 6px rgba(96, 165, 250, .3);
    pointer-events: none;
    z-index: 0;
    /* Subtler on phones: the big corner flakes read as too heavy on a narrow card, so halve
       them by default and restore the fuller look on wider screens below. */
    opacity: .5;
}

.bag-card-frozen::before {
    top: -18px;
    right: -6px;
    font-size: 96px;
}

.bag-card-frozen::after {
    bottom: -22px;
    left: -8px;
    font-size: 84px;
    transform: rotate(18deg);
}

@media (min-width: 768px) {
    .bag-card-frozen::before,
    .bag-card-frozen::after {
        opacity: 1;
    }
}

/* Lift the real content above the frost. */
.bag-card-frozen > .card-body {
    position: relative;
    z-index: 1;
}

/* On a frozen card the action buttons (Thaw, Finished) read icy-blue instead of the warm
   terracotta accent, so they belong to the frost. Overrides Bootstrap's own button vars. */
.bag-card-frozen .btn-outline-primary,
.bag-card-frozen .btn-outline-secondary {
    --bs-btn-color: #1d4ed8;
    --bs-btn-border-color: rgba(37, 99, 235, .55);
    --bs-btn-hover-color: #fff;
    --bs-btn-hover-bg: #2563eb;
    --bs-btn-hover-border-color: #2563eb;
    --bs-btn-active-color: #fff;
    --bs-btn-active-bg: #1d4ed8;
    --bs-btn-active-border-color: #1d4ed8;
    --bs-btn-disabled-color: #1d4ed8;
    --bs-btn-disabled-border-color: rgba(37, 99, 235, .55);
}

/* Collapsed bag: the whole card body is one tap-target that expands it. Reset the button chrome
   (it's a <button> for keyboard/AX) so it reads as the card it replaces, and nudge the chevron. */
.bag-collapsed-toggle {
    width: 100%;
    border: 0;
    background: transparent;
    text-align: inherit;
    color: inherit;
    cursor: pointer;
}

.bag-collapsed-toggle > .bi-chevron-down {
    color: var(--accent);
    flex: none;
}

/* On a frozen collapsed card the chevron reads icy-blue to match the frost. */
.bag-card-frozen .bag-collapsed-toggle > .bi-chevron-down {
    color: #1d4ed8;
}

/* The expanded card's "Collapse" link. */
.bag-collapse-btn {
    text-decoration: none;
    font-weight: 500;
}

/* Icy-blue on a frozen card so it belongs to the frost. */
.bag-collapse-btn-frozen {
    --bs-btn-color: #1d4ed8;
    --bs-btn-hover-color: #2563eb;
}

/* A single icy light-sweep the moment a bag is marked frozen. The page renders the .frost-sweep
   element only right after the freeze action (both corner flakes own ::before/::after), so it
   plays once and never on a plain page load. */
.frost-sweep {
    position: absolute;
    inset: 0;
    background: linear-gradient(115deg, transparent 35%, rgba(255, 255, 255, .6) 50%, transparent 65%);
    transform: translateX(-120%);
    animation: frost-sweep-anim 1.1s ease-out 1;
    pointer-events: none;
    z-index: 2;
}

@keyframes frost-sweep-anim {
    from { transform: translateX(-120%); }
    to { transform: translateX(120%); }
}

/* The what's-new banner borrowing the frozen-bag frost (Frosty announcements). Reuses
   .bag-card-frozen; these only retune the banner's own bits (the "New" badge and link) to the
   icy palette so the terracotta accent doesn't fight the cold look. Scoped to .whatsnew-frost so
   real bag cards are untouched. */
.whatsnew-frost .badge.text-bg-primary {
    background-color: #2563eb !important;
    color: #fff;
}

.whatsnew-frost a {
    color: #1d4ed8;
    font-weight: 600;
    text-decoration-color: rgba(37, 99, 235, .5);
}

/* ---- When-you-brew heatmap (My stats page) --------------------------------------- */
/* 7 weekday rows × 24 hour cells plus a label column and an hour-tick row. Cell
   intensity rides the per-cell --v custom property (0..1) set inline by the page. */
.brew-heatmap-wrap {
    overflow-x: auto;
}

.brew-heatmap {
    display: grid;
    grid-template-columns: 2.6rem repeat(24, minmax(.85rem, 1fr));
    gap: 2px;
    align-items: center;
    min-width: 34rem;
    max-width: 46rem;
}

.brew-heatmap .hm-day,
.brew-heatmap .hm-hour {
    font-size: .7rem;
    color: var(--muted);
}

.brew-heatmap .hm-cell {
    aspect-ratio: 1;
    border-radius: 3px;
    background: rgba(var(--accent-rgb), var(--v, 0));
    box-shadow: inset 0 0 0 1px var(--line);
}

/* ---- Taste passport chips (My stats page) ----------------------------------------- */
.passport-group {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .4rem;
    margin-bottom: .75rem;
}

.passport-title {
    font-size: .8rem;
    text-transform: uppercase;
    letter-spacing: .03em;
    color: var(--muted);
    margin-right: .35rem;
    min-width: 5.2rem;
}

.facet-chip {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .2rem .6rem;
    border-radius: 999px;
    background: var(--surface);
    border: 1px solid var(--line);
    font-size: .85rem;
}

.facet-chip .facet-count {
    color: var(--muted);
    font-size: .75rem;
}

/* ---- Coffee Journey: tasting-note chips + glossary ---------------------------------- */
/* A bag's tasting notes as chips (TastingNoteChips). Base sizing is the mobile touch
   target; desktop slims them back to facet-chip density. */
.note-chips {
    display: inline-flex;
    flex-wrap: wrap;
    gap: .4rem;
    vertical-align: middle;
}

.note-chip {
    display: inline-flex;
    align-items: center;
    gap: .3rem;
    padding: .3rem .7rem;
    min-height: 2.75rem; /* 44px touch target on phones */
    border-radius: 999px;
    background: var(--surface);
    border: 1px solid var(--line);
    font-size: .85rem;
    color: var(--ink);
}

/* Chips the glossary recognizes are doors: dotted underline as the tap affordance. */
button.note-chip-linked {
    cursor: pointer;
    text-decoration: underline dotted rgba(var(--accent-rgb), .6);
    text-underline-offset: .2em;
}

button.note-chip-linked:hover,
button.note-chip-linked:focus-visible {
    border-color: var(--accent);
    color: var(--accent);
}

@media (min-width: 768px) {
    .note-chip {
        min-height: 1.9rem;
        padding: .2rem .6rem;
    }
}

/* RatingPills — the 1-10 coffee rating scale. Mobile-first: a 5-column grid (two rows of
   five, 44px targets); one row of ten from tablet up. Active pill wears the accent. */
.rating-pills {
    display: grid;
    grid-template-columns: repeat(5, 1fr);
    gap: .4rem;
    max-width: 26rem;
}

.rating-pill {
    min-height: 2.75rem; /* 44px touch target on phones */
    border-radius: var(--radius-sm);
    background: var(--surface);
    border: 1px solid var(--line-strong);
    color: var(--ink);
    font-family: var(--font-data);
    font-size: .95rem;
    cursor: pointer;
}

.rating-pill:hover,
.rating-pill:focus-visible {
    border-color: var(--accent);
    color: var(--accent);
}

.rating-pill-active,
.rating-pill-active:hover,
.rating-pill-active:focus-visible {
    background: var(--accent);
    border-color: var(--accent);
    color: var(--accent-contrast);
}

.rating-pill:disabled {
    opacity: .55;
    cursor: default;
}

@media (min-width: 768px) {
    .rating-pills {
        grid-template-columns: repeat(10, minmax(2.4rem, 1fr));
    }

    .rating-pill {
        min-height: 2.2rem;
    }
}

/* The Coffee Journey editorial block: shared identity for glossary entries and (later)
   generated insights — an eyebrow with a trailing rule, a serif term, a lead line. */
.journey-eyebrow {
    display: flex;
    align-items: center;
    gap: .5rem;
    font-size: .72rem;
    letter-spacing: .14em;
    text-transform: uppercase;
    color: var(--muted);
}

.journey-eyebrow::after {
    content: "";
    flex: 1;
    height: 1px;
    background: var(--line-strong);
}

.journey-term {
    font-family: var(--font-display);
}

.journey-lead {
    font-size: 1.02rem;
    font-weight: 500;
}

/* One "try next" pick in the journey narrative: the style on top, its reason below. */
.journey-trynext {
    padding: .55rem 0;
    border-top: 1px solid var(--line);
}

.journey-trynext:first-child {
    border-top: 0;
    padding-top: .2rem;
}

/* "What is this based on?" transparency disclosure under the narrative: a quiet toggle that
   opens the exact passport context the story was written from. Palette variables only. */
.journey-basis {
    border-top: 1px solid var(--line);
    padding-top: .6rem;
}

.journey-basis-toggle {
    display: inline-flex;
    align-items: center;
    gap: .35rem;
    min-height: 44px;
    padding: 0;
    border: 0;
    background: none;
    color: var(--muted);
    font-size: .82rem;
    font-weight: 600;
}

.journey-basis-toggle:hover,
.journey-basis-toggle:focus-visible {
    color: var(--accent);
}

.journey-basis-toggle .bi {
    font-size: .8rem;
}

.journey-basis-body {
    padding: .7rem .85rem;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    font-size: .85rem;
}

/* Label above / value below on phones; two columns once there's room. */
.journey-basis-list {
    display: grid;
    grid-template-columns: 1fr;
    gap: .1rem .8rem;
}

.journey-basis-list dt {
    font: 600 10px var(--font-body);
    letter-spacing: .09em;
    text-transform: uppercase;
    color: var(--muted);
    margin-top: .4rem;
}

.journey-basis-list dt:first-child {
    margin-top: 0;
}

.journey-basis-list dd {
    margin: 0;
}

.journey-basis-note-coffee {
    font-weight: 600;
}

@media (min-width: 576px) {
    .journey-basis-list {
        grid-template-columns: auto 1fr;
    }

    .journey-basis-list dt {
        margin-top: 0;
        padding-top: .1rem;
    }
}

/* Catalog quick-look drawer's "your journey" strip: carries the Coffee Journey identity (the
   compass eyebrow + a Fraunces read-line, like the branded journey cards) so it reads as part of
   the suite, with a kind-tinted chip (match / new / familiar) for a glanceable read of how the
   coffee fits the viewer's passport. */
.journey-fit {
    padding: .7rem .8rem;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
}

.journey-fit-eyebrow {
    margin-bottom: .55rem;
    font-size: .66rem;
}

/* The compass mark that leads every Coffee Journey eyebrow (only where an icon is present, so the
   glossary category eyebrows that reuse this class are untouched). */
.journey-eyebrow .bi {
    font-size: .85rem;
    color: var(--accent);
}

.journey-fit-chip {
    display: inline-block;
    font-size: .68rem;
    font-weight: 600;
    letter-spacing: .06em;
    text-transform: uppercase;
    padding: .14rem .55rem;
    border-radius: 999px;
    background: rgba(var(--chip-secondary-rgb), .18);
    color: var(--ink);
}

.journey-fit-match .journey-fit-chip {
    background: rgba(var(--warning-rgb), .28);
}

.journey-fit-new .journey-fit-chip {
    background: rgba(var(--accent-rgb), .16);
    color: var(--accent-active);
}

.journey-fit-line {
    margin: .45rem 0 0;
    font-family: var(--font-display);
    font-size: .92rem;
    font-weight: 500;
    color: var(--ink);
    line-height: 1.4;
}

.journey-fit-foot {
    margin-top: .55rem;
}

.journey-fit-link {
    font-size: .8rem;
    font-weight: 600;
    white-space: nowrap;
}

/* Coffee Journey recommendation rows: a small thumb + name/facets/reason, the whole row a link. */
.reco-list {
    display: flex;
    flex-direction: column;
    gap: .5rem;
}

.reco-row {
    display: flex;
    gap: .75rem;
    align-items: flex-start;
    padding: .55rem;
    border: 1px solid var(--line);
    border-radius: var(--radius-sm);
    text-decoration: none;
    color: inherit;
}

.reco-row:hover,
.reco-row:focus-visible {
    border-color: var(--line-strong);
    background: var(--surface-2, rgba(0, 0, 0, .02));
}

.reco-thumb {
    width: 44px;
    height: 44px;
    border-radius: var(--radius-sm);
    object-fit: cover;
    flex-shrink: 0;
}

.reco-body {
    min-width: 0;
}

.reco-name {
    font-weight: 600;
}

.reco-reason {
    color: var(--fg);
}

/* Free/preview tease: the picks past the first are placeholder rows, blurred and inert. */
.reco-blurred {
    filter: blur(4px);
    opacity: .55;
    pointer-events: none;
    user-select: none;
}

.reco-ph-bar {
    height: .55rem;
    border-radius: 4px;
    background: var(--line-strong);
    margin-top: .4rem;
}

.reco-ph-name {
    width: 55%;
    height: .8rem;
    margin-top: .1rem;
}

.reco-ph-sub {
    width: 35%;
}

.reco-ph-reason {
    width: 80%;
}

/* The locked face of a Journey read: the tease card free users see. */
.journey-locked {
    border: 1px dashed var(--line-strong);
    border-radius: var(--radius-sm);
    padding: .9rem;
    background: rgba(var(--warning-rgb), .07);
}

/* Buy-or-Skip scan verdict: the point-of-purchase card. A big match score paired with a one-word
   verdict, the grounded reasons below, and a forward-looking dial-in hint. Kind-tinted left edge
   (solid / promising / mixed / risky) for a glanceable read. Mobile-first: the head stacks score
   beside label, everything else flows in one column. */
/* The brand coffee mark reused as a currentColor icon (e.g. in buttons), so the app has one cup
   shape rather than a separate Bootstrap cup alongside the logo. A mask keeps it legible and
   correctly coloured on both filled and outline buttons. */
.brand-cup {
    display: inline-block;
    width: 1em;
    height: 1em;
    vertical-align: -0.15em;
    background-color: currentColor;
    -webkit-mask: url("login-logo.svg") center / contain no-repeat;
    mask: url("login-logo.svg") center / contain no-repeat;
}

.scan-verdict-title {
    font-family: var(--font-display);
    font-size: 1.1rem;
    font-weight: 600;
    margin-bottom: .6rem;
}

.scan-verdict {
    border: 1px solid var(--line);
    border-left: 4px solid var(--line-strong);
    border-radius: var(--radius-sm);
    padding: .85rem .95rem;
    background: var(--accent-soft);
}

.scan-verdict-solid { border-left-color: rgb(var(--success-rgb)); }
.scan-verdict-promising { border-left-color: rgb(var(--accent-rgb)); }
.scan-verdict-mixed { border-left-color: var(--line-strong); }
.scan-verdict-risky { border-left-color: rgb(var(--warning-rgb)); }

.scan-verdict-head {
    display: flex;
    align-items: center;
    gap: .8rem;
}

.scan-verdict-score {
    font-family: var(--font-display);
    font-size: 2.2rem;
    font-weight: 700;
    line-height: 1;
    color: var(--ink);
}

.scan-verdict-score span {
    font-size: 1.1rem;
    font-weight: 600;
    color: var(--muted);
}

.scan-verdict-solid .scan-verdict-score { color: rgb(var(--success-rgb)); }
.scan-verdict-risky .scan-verdict-score { color: rgb(var(--warning-rgb)); }

.scan-verdict-label {
    font-family: var(--font-display);
    font-size: 1.15rem;
    font-weight: 600;
    color: var(--ink);
}

.scan-verdict-sub {
    font-size: .78rem;
    letter-spacing: .04em;
    text-transform: uppercase;
    color: var(--muted);
}

.scan-verdict-reasons {
    list-style: none;
    margin: .75rem 0 0;
    padding: 0;
}

.scan-verdict-reasons li {
    position: relative;
    padding: .28rem 0 .28rem 1.1rem;
    font-size: .92rem;
    border-top: 1px solid var(--line);
}

.scan-verdict-reasons li:first-child {
    border-top: 0;
}

.scan-verdict-reasons li::before {
    content: "•";
    position: absolute;
    left: .2rem;
    color: var(--accent);
}

.scan-verdict-hint {
    margin: .75rem 0 0;
    padding-top: .65rem;
    border-top: 1px solid var(--line-strong);
    font-size: .9rem;
    line-height: 1.45;
}

.scan-verdict-hint .micro-label {
    display: block;
    margin-bottom: .1rem;
}

/* ---- Dial-In Coach --------------------------------------------------------------- */
/* The guided dial-in loop: a small "Dial-in coach" tag on the Coffee Journey eyebrow, a
   vertical convergence trail (v1 -> v2 -> ...), the one change to try next, and the
   meta-insight nudge. Mobile-first; palette variables only. */
.coach-tag {
    font-size: .62rem;
    font-weight: 600;
    letter-spacing: .1em;
    padding: .1rem .45rem;
    border-radius: 999px;
    background: var(--accent-soft);
    color: var(--accent-active);
}

.coach-trail {
    list-style: none;
    margin: .4rem 0 0;
    padding: 0;
}

.coach-step {
    display: flex;
    align-items: baseline;
    gap: .6rem;
    padding: .4rem 0;
    border-top: 1px solid var(--line);
}

.coach-step:first-child {
    border-top: 0;
}

.coach-ver {
    flex-shrink: 0;
    min-width: 2.1rem;
    font-family: var(--font-display);
    font-weight: 600;
    color: var(--accent-active);
}

.coach-step-body {
    display: flex;
    flex-wrap: wrap;
    align-items: baseline;
    gap: .4rem .6rem;
    min-width: 0;
}

.coach-change {
    font-size: .68rem;
    font-weight: 600;
    letter-spacing: .05em;
    text-transform: uppercase;
    padding: .1rem .5rem;
    border-radius: 999px;
    background: rgba(var(--accent-rgb), .14);
    color: var(--accent-active);
}

.coach-taste {
    color: var(--muted);
    font-size: .92rem;
}

/* The one change to make next: the actionable card the whole coach converges on. */
.coach-next {
    padding: .8rem;
    border-radius: var(--radius-sm);
    background: var(--accent-soft);
    border: 1px solid var(--line-strong);
}

.coach-next-change {
    margin: .15rem 0 .5rem;
    font-family: var(--font-display);
    font-size: 1.02rem;
    font-weight: 600;
    color: var(--ink);
}

/* The "it's probably not the recipe" meta-insight. */
.coach-meta {
    display: flex;
    gap: .55rem;
    align-items: flex-start;
    padding: .7rem .8rem;
    border-radius: var(--radius-sm);
    background: rgba(var(--warning-rgb), .12);
    font-size: .92rem;
}

.coach-meta .bi {
    color: rgb(var(--warning-rgb));
    flex-shrink: 0;
}

.coach-converged {
    display: flex;
    gap: .55rem;
    align-items: center;
    font-weight: 500;
    color: var(--ink);
}

.coach-converged .bi {
    color: var(--accent);
    flex-shrink: 0;
}

/* ---- Premium preview (My stats page) ----------------------------------------------- */
/* The real heatmap/passport widgets rendered with sample data for free users: content is
   frosted and inert, a small pitch card floats on top and carries the way in. */
.premium-preview {
    position: relative;
}

.premium-preview-content {
    filter: blur(2px) saturate(.75);
    opacity: .65;
    pointer-events: none;
    user-select: none;
}

.premium-preview-overlay {
    position: absolute;
    inset: 0;
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 1rem;
}

.premium-preview-pitch {
    background: var(--surface);
    border: 1px solid var(--line);
    border-radius: .75rem;
    box-shadow: 0 1px 3px rgba(var(--ink-rgb), .35);
    padding: 1rem 1.25rem;
    text-align: center;
    max-width: 24rem;
}

/* On phones the centered card would hide most of the widget it's advertising — drop it
   into normal flow below the frosted content instead, so the sample stays fully visible. */
@media (max-width: 575.98px) {
    .premium-preview-overlay {
        position: static;
        padding: .5rem 0 0;
    }

    .premium-preview-pitch {
        max-width: none;
        width: 100%;
    }
}

/* ---- Brew map (My stats page) ------------------------------------------------------ */
/* The inlined world SVG: land defaults to the palette's line tone, brewed countries are
   filled accent by a generated per-id style block (see WorldMap.razor). The default fill
   sits on the svg root and INHERITS down — multi-part countries are <g id="xx"> groups,
   and a direct `path` rule would beat the group-level highlight. */
.world-map {
    max-width: 52rem;
}

.world-map svg {
    width: 100%;
    height: auto;
    display: block;
    fill: var(--line-strong);
}

.world-map-credit {
    font-size: .65rem;
    color: var(--muted);
    text-align: right;
    margin-top: .15rem;
}

.world-map-credit a {
    color: inherit;
}

/* ---- Misc (template defaults, kept) --------------------------------------------- */
.content {
    padding-top: 1.1rem;
}

h1:focus {
    outline: none;
}

.valid.modified:not([type=checkbox]) {
    outline: 1px solid #26b050;
}

.invalid {
    outline: 1px solid #e50000;
}

.validation-message {
    color: #e50000;
}

.blazor-error-boundary {
    background: url(data:image/svg+xml;base64,PHN2ZyB3aWR0aD0iNTYiIGhlaWdodD0iNDkiIHhtbG5zPSJodHRwOi8vd3d3LnczLm9yZy8yMDAwL3N2ZyIgeG1sbnM6eGxpbms9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkveGxpbmsiIG92ZXJmbG93PSJoaWRkZW4iPjxkZWZzPjxjbGlwUGF0aCBpZD0iY2xpcDAiPjxyZWN0IHg9IjIzNSIgeT0iNTEiIHdpZHRoPSI1NiIgaGVpZ2h0PSI0OSIvPjwvY2xpcFBhdGg+PC9kZWZzPjxnIGNsaXAtcGF0aD0idXJsKCNjbGlwMCkiIHRyYW5zZm9ybT0idHJhbnNsYXRlKC0yMzUgLTUxKSI+PHBhdGggZD0iTTI2My41MDYgNTFDMjY0LjcxNyA1MSAyNjUuODEzIDUxLjQ4MzcgMjY2LjYwNiA1Mi4yNjU4TDI2Ny4wNTIgNTIuNzk4NyAyNjcuNTM5IDUzLjYyODMgMjkwLjE4NSA5Mi4xODMxIDI5MC41NDUgOTIuNzk1IDI5MC42NTYgOTIuOTk2QzI5MC44NzcgOTMuNTEzIDI5MSA5NC4wODE1IDI5MSA5NC42NzgyIDI5MSA5Ny4wNjUxIDI4OS4wMzggOTkgMjg2LjYxNyA5OUwyNDAuMzgzIDk5QzIzNy45NjMgOTkgMjM2IDk3LjA2NTEgMjM2IDk0LjY3ODIgMjM2IDk0LjM3OTkgMjM2LjAzMSA5NC4wODg2IDIzNi4wODkgOTMuODA3MkwyMzYuMzM4IDkzLjAxNjIgMjM2Ljg1OCA5Mi4xMzE0IDI1OS40NzMgNTMuNjI5NCAyNTkuOTYxIDUyLjc5ODUgMjYwLjQwNyA1Mi4yNjU4QzI2MS4yIDUxLjQ4MzcgMjYyLjI5NiA1MSAyNjMuNTA2IDUxWk0yNjMuNTg2IDY2LjAxODNDMjYwLjczNyA2Ni4wMTgzIDI1OS4zMTMgNjcuMTI0NSAyNTkuMzEzIDY5LjMzNyAyNTkuMzEzIDY5LjYxMDIgMjU5LjMzMiA2OS44NjA4IDI1OS4zNzEgNzAuMDg4N0wyNjEuNzk1IDg0LjAxNjEgMjY1LjM4IDg0LjAxNjEgMjY3LjgyMSA2OS43NDc1QzI2Ny44NiA2OS43MzA5IDI2Ny44NzkgNjkuNTg3NyAyNjcuODc5IDY5LjMxNzkgMjY3Ljg3OSA2Ny4xMTgyIDI2Ni40NDggNjYuMDE4MyAyNjMuNTg2IDY2LjAxODNaTTI2My41NzYgODYuMDU0N0MyNjEuMDQ5IDg2LjA1NDcgMjU5Ljc4NiA4Ny4zMDA1IDI1OS43ODYgODkuNzkyMSAyNTkuNzg2IDkyLjI4MzcgMjYxLjA0OSA5My41Mjk1IDI2My41NzYgOTMuNTI5NSAyNjYuMTE2IDkzLjUyOTUgMjY3LjM4NyA5Mi4yODM3IDI2Ny4zODcgODkuNzkyMSAyNjcuMzg3IDg3LjMwMDUgMjY2LjExNiA4Ni4wNTQ3IDI2My41NzYgODYuMDU0N1oiIGZpbGw9IiNGRkU1MDAiIGZpbGwtcnVsZT0iZXZlbm9kZCIvPjwvZz48L3N2Zz4=) no-repeat 1rem/1.8rem, #b32121;
    padding: 1rem 1rem 1rem 3.7rem;
    color: white;
}

    .blazor-error-boundary::after {
        content: "An error has occurred."
    }

.darker-border-checkbox.form-check-input {
    border-color: #929292;
}

.form-floating > .form-control-plaintext::placeholder, .form-floating > .form-control::placeholder {
    color: var(--bs-secondary-color);
    text-align: end;
}

.form-floating > .form-control-plaintext:focus::placeholder, .form-floating > .form-control:focus::placeholder {
    text-align: start;
}

/* Full-width on phones, natural (inline) width from the sm breakpoint up. */
@media (max-width: 575.98px) {
    .btn-full-mobile {
        display: block;
        width: 100%;
    }
}

/* Shared stock placeholder for a coffee photo: shown anywhere a real photo would appear when
   the coffee has none, so layouts stay aligned. Size/shape come from the caller (Class/Style,
   same as the <img> it replaces); this rule only supplies the fill, centering and glyph. */
.coffee-photo-ph {
    display: flex;
    align-items: center;
    justify-content: center;
    flex: 0 0 auto;
    overflow: hidden;
    background: var(--surface-2);
    color: var(--muted);
}

.coffee-photo-ph .bi {
    font-size: 1.5rem;
    opacity: .5;
}
