/*
 * Gardening Journal - Site-Wide CSS
 *
 * Design principles:
 * - Mobile-first responsive design
 * - iOS-style photo grid with natural aspect ratios
 * - TurboTax-style centered dialogs for simple screens
 * - Optimal line length (50-75 chars) for notes
 * - Light and dark mode support
 */

/* ==========================================================================
   Fonts

   The rule: Concourse (sans) for what you operate — headings (h1-h3),
   buttons, menu items, form labels; Equity B (serif) for what you
   read — body, prose, notes, captions.  Equity ships in 400,
   400 italic, and 700; declared weights of 500/600 in Equity contexts
   resolve to the nearest real weight (400 and 700) by the CSS
   font-matching algorithm, so nothing is synthesized.
   See docs/style-guide.md.

   Font files use a manual -v1 version suffix instead of a content hash
   (they never change; bump the suffix if they ever do) and are exempted
   from hash renaming in assets/gen-assets.sh.  font-display: swap so text
   renders immediately for cold-cache visitors (shared story links).
   ========================================================================== */

@font-face {
    font-family: "Concourse";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("fonts/concourse-regular-v1.woff2") format("woff2");
}

@font-face {
    font-family: "Concourse";
    font-style: normal;
    font-weight: 500;
    font-display: swap;
    src: url("fonts/concourse-medium-v1.woff2") format("woff2");
}

@font-face {
    font-family: "Concourse";
    font-style: normal;
    font-weight: 600;
    font-display: swap;
    src: url("fonts/concourse-semibold-v1.woff2") format("woff2");
}

@font-face {
    font-family: "Concourse";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("fonts/concourse-bold-v1.woff2") format("woff2");
}

@font-face {
    font-family: "Equity";
    font-style: normal;
    font-weight: 400;
    font-display: swap;
    src: url("fonts/equity-b-regular-v1.woff2") format("woff2");
}

@font-face {
    font-family: "Equity";
    font-style: italic;
    font-weight: 400;
    font-display: swap;
    src: url("fonts/equity-b-italic-v1.woff2") format("woff2");
}

@font-face {
    font-family: "Equity";
    font-style: normal;
    font-weight: 700;
    font-display: swap;
    src: url("fonts/equity-b-bold-v1.woff2") format("woff2");
}

/* ==========================================================================
   CSS Variables - Color Palette
   ========================================================================== */

:root {
    /* Primary - Teal */
    --teal-15: #014D40;
    --teal-23: #0C6B58;
    --teal-28: #147D64;
    --teal-34: #199473;
    --teal-41: #27AB83;
    --teal-49: #3EBD93;
    --teal-62: #65D6AD;
    --teal-74: #8EEDC7;
    --teal-87: #C6F7E2;
    --teal-96: #EFFCF6;

    /* Neutrals - Blue Grey */
    --grey-16: #102A43;
    --grey-23: #243B53;
    --grey-30: #334E68;
    --grey-39: #486581;
    --grey-49: #627D98;
    --grey-60: #829AB1;
    --grey-70: #9FB3C8;
    --grey-80: #BCCCDC;
    --grey-89: #D9E2EC;
    --grey-96: #F0F4F8;

    /* Supporting - Red */
    --red-20: #610404;
    --red-25: #780A0A;
    --red-32: #911111;
    --red-38: #A61B1B;
    --red-44: #BA2525;
    --red-55: #D64545;
    --red-66: #E66A6A;
    /* Off the scale's usual desaturation curve on purpose: dark mode's
       danger colour has to clear 3:1 as a glyph on a card without going
       pale. 50% saturation, 3.78 on a dark card, 5.04 on the page. */
    --red-75: #FF8A80;
    --red-78: #F29B9B;
    --red-89: #FACDCD;
    --red-97: #FFEEEE;

    /* Supporting - Blue */
    --blue-21: #003E6B;
    --blue-29: #0A558C;
    --blue-33: #0F609B;
    --blue-39: #186FAF;
    --blue-45: #2680C2;
    --blue-55: #4098D7;
    --blue-65: #62B0E8;
    --blue-75: #84C5F4;
    --blue-85: #B6E0FE;
    --blue-92: #DCEEFB;

    /* Supporting - Yellow */
    --yellow-17: #513C06;
    --yellow-27: #7C5E10;
    --yellow-37: #A27C1A;
    --yellow-48: #C99A2E;
    --yellow-60: #E9B949;
    --yellow-70: #F7D070;
    --yellow-76: #F9DA8B;
    --yellow-81: #F8E3A3;
    --yellow-88: #FCEFC7;
    --yellow-92: #FDF3D9;
    --yellow-96: #FFFAEB;

    /* Supporting - Purple */
    --purple-18: #240754;
    --purple-25: #34126F;
    --purple-31: #421987;
    --purple-38: #51279B;
    --purple-46: #653CAD;
    --purple-51: #724BB7;
    --purple-58: #8662C7;
    --purple-68: #A081D9;
    --purple-84: #CFBCF2;
    --purple-93: #EAE2F8;

    /* Semantic - Light Mode (default) */
    --color-bg: var(--yellow-92);
    /* Surfaces sit ABOVE the page: a card or control is shaped by a fill
       lighter than --color-bg, never by a border. See docs/style-guide.md. */
    --color-surface: var(--yellow-96);
    --color-bg-elevated: var(--yellow-88);
    --color-bg-subtle: var(--yellow-88);
    --color-text: var(--grey-23);
    --color-text-secondary: var(--grey-49);
    --color-text-muted: var(--grey-60);
    --color-border: var(--grey-70);
    --color-border-subtle: var(--grey-96);
    /* Control outlines need 3:1 against the page (WCAG 1.4.11).
       --color-border (grey-70) is 1.95:1 and cannot carry a control. */
    --color-border-control: var(--grey-49);
    /* teal-41 was 2.62:1 against the light page and white on it was
       2.77:1 — the commit chip failed the same threshold the neutrals
       did. teal-34 is the only token clearing 3:1 against BOTH page
       colours; hover and active step down with it so hover does not
       collapse into base. Dark mode already passes at teal-49. */
    --color-primary: var(--teal-34);
    --color-primary-hover: var(--teal-28);
    --color-primary-active: var(--teal-23);
    --color-error: var(--red-44);
    --color-danger: var(--red-44);
    --color-error-bg: var(--red-97);
    --color-warning-bg: var(--yellow-81);
    --color-warning-border: var(--yellow-60);
    --color-link: var(--blue-39);
    --color-link-hover: var(--blue-21);
    --color-accent: var(--teal-49);
    --color-text-inverse: var(--yellow-96);
    --color-backdrop: var(--grey-16);
    --color-overlay: color-mix(in srgb, var(--grey-16) 50%, transparent);
    --color-overlay-badge: color-mix(in srgb, var(--grey-16) 50%, transparent);
    --color-shadow: color-mix(in srgb, var(--grey-16) 20%, transparent);
    --color-shadow-heavy: color-mix(in srgb, var(--grey-16) 15%, transparent);
    --color-input-bg: var(--color-surface);
    --color-focus-ring: color-mix(in srgb, var(--teal-28) 20%, transparent);
    --color-navbar-bg: var(--purple-93);
    --color-nav-inactive: var(--grey-39);
    --color-nav-inactive-hover: var(--color-primary);

    /* Layout */
    --header-height: 56px;
    --tab-bar-height: 56px;
    --content-max-width: 900px;
    --measure: 70ch; /* Prose reading width (60-90ch is acceptable) */
    --note-max-width: var(--measure);
    /* Every photo is this long on its longest edge, whichever edge that
       is: a landscape is this wide, a portrait this tall. Naming it
       "max-width" was a lie for portrait photos. */
    --photo-long-side: 480px;
    --photo-gap: 2px;
    --spacing-xs: 0.25rem;
    --spacing-sm: 0.5rem;
    --spacing-md: 1rem;
    --spacing-lg: 1.5rem;
    --spacing-xl: 2rem;
    --radius-sm: 4px;
    --radius-md: 8px;
    --radius-lg: 12px;

    /* Typography */
    --font-family: 'Equity', Georgia, 'Times New Roman', serif;
    --font-family-prose: 'Equity', Georgia, 'Times New Roman', serif;
    --font-family-headings: 'Concourse', -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, Oxygen, Ubuntu, Cantarell, sans-serif;
    --font-size-xs: 0.75rem;
    --font-size-sm: 0.875rem;
    --font-size-base: 1rem;
    --font-size-lg: 1.125rem;
    --font-size-xl: 1.25rem;
    --font-size-2xl: 1.5rem;
    --font-size-3xl: 2rem;
    --line-height-tight: 1.25;
    --line-height-normal: 1.5;
    --line-height-relaxed: 1.75;
}

/* Dark Mode */
@media (prefers-color-scheme: dark) {
    :root {
        --color-bg: var(--grey-23);
        --color-surface: var(--grey-30);
        --color-bg-elevated: var(--grey-30);
        --color-bg-subtle: var(--grey-39);
        --color-text: var(--yellow-96);
        --color-text-secondary: var(--grey-70);
        --color-text-muted: var(--grey-60);
        --color-border: var(--grey-39);
        --color-border-subtle: var(--grey-30);
        /* grey-60 was 3.95 on the dark page but only 2.97 on a dark
           card, and a card is where most controls sit. Measure a control
           token against the CARD, not the page. grey-70: 5.34 / 4.01. */
        --color-border-control: var(--grey-70);
        --color-primary: var(--teal-49);
        --color-primary-hover: var(--teal-62);
        --color-primary-active: var(--teal-74);
        --color-error: var(--red-66);
        --color-danger: var(--red-75);
        --color-error-bg: var(--red-20);
        --color-warning-bg: var(--yellow-17);
        --color-warning-border: var(--yellow-48);
        --color-link: var(--blue-65);
        --color-link-hover: var(--blue-75);
        --color-accent: var(--teal-62);
        --color-focus-ring: color-mix(in srgb, var(--teal-49) 20%, transparent);
        --color-navbar-bg: var(--purple-25);
        --color-nav-inactive: var(--grey-60);
        --color-nav-inactive-hover: var(--color-primary);
    }
}

/* ==========================================================================
   Reset & Base
   ========================================================================== */

*, *::before, *::after {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

/* The UA's own `[hidden] { display: none }` is a bare element rule, so
   any class that sets display beats it on specificity — and .btn and
   .action-btn both set `display: inline-flex`. Without this, the hidden
   attribute silently does nothing on any button in this design system,
   which is how the Account edit session would have rendered back, check
   and every row's Delete while the screen was at rest. Approved
   2026-08-24. */
[hidden] { display: none !important; }

html {
    font-size: 16px;
    -webkit-text-size-adjust: 100%;
}

body {
    font-family: var(--font-family);
    font-size: var(--font-size-xl); /* body text IS prose; Equity runs small */
    line-height: var(--line-height-relaxed);
    color: var(--color-text);
    background-color: var(--color-bg);
    min-height: 100vh;
    min-height: 100dvh; /* Dynamic viewport height for mobile */
}

img {
    max-width: 100%;
    height: auto;
    display: block;
}

/* HEADING: one style, three sizes (L 2rem / M 1.5rem / S 1.25rem).
   Concourse 600 (700 reads too heavy), 0.02em letter-spacing.
   Rhythm: 2:1 space above vs. below binds a heading to the content it
   introduces; h1 sits at the top of the page (no space above).
   See docs/style-guide.md. */
h1 {
    font-family: var(--font-family-headings);
    font-size: var(--font-size-3xl);
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: var(--line-height-tight);
    margin: 0 0 1rem;
}

h2 {
    font-family: var(--font-family-headings);
    font-size: var(--font-size-2xl);
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: var(--line-height-tight);
    margin: 2rem 0 0.75rem;
}

h3 {
    font-family: var(--font-family-headings);
    font-size: var(--font-size-xl);
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: var(--line-height-tight);
    color: var(--color-text-secondary);
}

/* Heading S as a class, for date headers on the journal timeline and
   the story subtitle — one class so they stay in sync. Carrier classes
   (.timeline-date, .story-date) keep only layout. */
.date-heading {
    font-family: var(--font-family-headings);
    font-size: var(--font-size-xl);
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: var(--line-height-tight);
    color: var(--color-text-secondary);
    text-transform: none;
}

a {
    color: var(--color-link);
    text-decoration: underline;
}

@media (hover: hover) {
    a:hover {
        color: var(--color-link-hover);
    }
}

.btn-link {
    background: none;
    border: none;
    padding: 0;
    color: var(--color-link);
    cursor: pointer;
    font-size: inherit;
    text-decoration: underline;
}

@media (hover: hover) {
    .btn-link:hover {
        color: var(--color-link-hover);
    }
}

/* Lists */
ol, ul {
    padding-left: 1.5rem;
    margin-bottom: var(--spacing-md);
}

ol li, ul li {
    margin-bottom: var(--spacing-sm);
    line-height: var(--line-height-relaxed);
}

ol li:last-child, ul li:last-child {
    margin-bottom: 0;
}

.emphasize {
    font-weight: 700; /* Equity has no 600; use its real bold */
}

/* ==========================================================================
   Page Layouts
   ========================================================================== */

/*
 * Centered layout - TurboTax style
 * For simple screens: Home, Login, Signup Success, etc.
 * Content centers both vertically and horizontally
 */
.page-centered {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
    justify-content: center;
    align-items: center;
    padding: var(--spacing-lg);
}

.page-centered-content {
    width: 100%;
    max-width: 400px;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--spacing-md);
}

/* Flex gap governs spacing here; the h1 rhythm margin would double it. */
.page-centered-content h1 {
    margin: 0;
}

/*
 * App layout - with sticky header and tab bar
 * For authenticated screens: Journal, Review, Explore, Settings
 */
.page-app {
    min-height: 100vh;
    min-height: 100dvh;
    display: flex;
    flex-direction: column;
}

.page-app-content {
    flex: 1;
    padding-top: var(--header-height);
    padding-bottom: var(--tab-bar-height);
    overflow-y: auto;
}

/* ==========================================================================
   Sticky Header
   ========================================================================== */

.header {
    position: fixed;
    top: 0;
    left: 0;
    right: 0;
    height: var(--header-height);
    background-color: var(--color-bg);
    display: flex;
    align-items: center;
    justify-content: center;
    padding: 0 var(--spacing-md);
    z-index: 100;
}

.header-inner {
    width: 100%;
    max-width: var(--content-max-width);
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.header-left {
    display: flex;
    align-items: baseline;
    gap: var(--spacing-sm);
}

.header-title {
    color: var(--color-text);
    margin: 0; /* h1 rhythm margin does not apply inside the sticky header */
}

.header-date {
    font-size: var(--font-size-sm);
    font-weight: 400;
    color: var(--color-text-muted);
}

.header-actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
}

.header-action {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    color: var(--color-primary);
    background: none;
    border: none;
    border-radius: var(--radius-md);
    cursor: pointer;
    font-size: var(--font-size-xl);
}

@media (hover: hover) {
    .header-action:hover {
        background-color: var(--color-bg-subtle);
    }
}

/* ==========================================================================
   Bottom Navbar (shared shell for app tab bar and content page nav)
   ========================================================================== */

.navbar {
    position: fixed;
    bottom: 0;
    left: 0;
    right: 0;
    height: var(--tab-bar-height);
    background-color: var(--color-bg);
    display: flex;
    align-items: center;
    justify-content: center;
    padding-bottom: env(safe-area-inset-bottom, 0);
    z-index: 100;
}

.navbar-inner {
    width: 100%;
    max-width: var(--content-max-width);
    padding: 0 var(--spacing-md);
    display: flex;
    align-items: center;
    justify-content: space-around;
}

/* -- App tab bar items --------------------------------------------------- */

.navbar-item {
    display: flex;
    flex-direction: column;
    align-items: center;
    justify-content: center;
    gap: 2px;
    padding: var(--spacing-xs);
    background: none;
    border: none;
    cursor: pointer;
    color: var(--color-nav-inactive);
    text-decoration: none;
    font-size: var(--font-size-xs);
    transition: color 0.15s;
}

/* Bottom tab bar items stretch to fill equal widths.
   The header "Add Photos" button also uses .navbar-item
   but should stay at its natural size. */
.navbar-inner .navbar-item {
    flex: 1;
}

@media (hover: hover) {
    .navbar-item:hover {
        text-decoration: none;
        color: var(--color-nav-inactive-hover);
    }
}

.navbar-item.active {
    color: var(--color-primary);
}

.navbar-icon {
    display: inline-block;
    width: 24px;
    height: 24px;
    background-color: currentColor;
    -webkit-mask-size: contain;
    mask-size: contain;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
}

.navbar-icon-home {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='%23000000' viewBox='0 0 256 256'%3E%3Cpath d='M224,120v96a8,8,0,0,1-8,8H160a8,8,0,0,1-8-8V164a4,4,0,0,0-4-4H108a4,4,0,0,0-4,4v52a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V120a16,16,0,0,1,4.69-11.31l80-80a16,16,0,0,1,22.62,0l80,80A16,16,0,0,1,224,120Z'%3E%3C/path%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='%23000000' viewBox='0 0 256 256'%3E%3Cpath d='M224,120v96a8,8,0,0,1-8,8H160a8,8,0,0,1-8-8V164a4,4,0,0,0-4-4H108a4,4,0,0,0-4,4v52a8,8,0,0,1-8,8H40a8,8,0,0,1-8-8V120a16,16,0,0,1,4.69-11.31l80-80a16,16,0,0,1,22.62,0l80,80A16,16,0,0,1,224,120Z'%3E%3C/path%3E%3C/svg%3E");
}

.navbar-icon-journal {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='%23000000' viewBox='0 0 256 256'%3E%3Cpath d='M232,48H168a32,32,0,0,0-32,32v87.73a8.17,8.17,0,0,1-7.47,8.25,8,8,0,0,1-8.53-8V80A32,32,0,0,0,88,48H24a8,8,0,0,0-8,8V200a8,8,0,0,0,8,8H96a24,24,0,0,1,24,23.94,7.9,7.9,0,0,0,5.12,7.55A8,8,0,0,0,136,232a24,24,0,0,1,24-24h72a8,8,0,0,0,8-8V56A8,8,0,0,0,232,48ZM208,168H168.27a8.17,8.17,0,0,1-8.25-7.47,8,8,0,0,1,8-8.53h39.73a8.17,8.17,0,0,1,8.25,7.47A8,8,0,0,1,208,168Zm0-32H168.27a8.17,8.17,0,0,1-8.25-7.47,8,8,0,0,1,8-8.53h39.73a8.17,8.17,0,0,1,8.25,7.47A8,8,0,0,1,208,136Zm0-32H168.27A8.17,8.17,0,0,1,160,96.53,8,8,0,0,1,168,88h39.73A8.17,8.17,0,0,1,216,95.47,8,8,0,0,1,208,104Z'%3E%3C/path%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='%23000000' viewBox='0 0 256 256'%3E%3Cpath d='M232,48H168a32,32,0,0,0-32,32v87.73a8.17,8.17,0,0,1-7.47,8.25,8,8,0,0,1-8.53-8V80A32,32,0,0,0,88,48H24a8,8,0,0,0-8,8V200a8,8,0,0,0,8,8H96a24,24,0,0,1,24,23.94,7.9,7.9,0,0,0,5.12,7.55A8,8,0,0,0,136,232a24,24,0,0,1,24-24h72a8,8,0,0,0,8-8V56A8,8,0,0,0,232,48ZM208,168H168.27a8.17,8.17,0,0,1-8.25-7.47,8,8,0,0,1,8-8.53h39.73a8.17,8.17,0,0,1,8.25,7.47A8,8,0,0,1,208,168Zm0-32H168.27a8.17,8.17,0,0,1-8.25-7.47,8,8,0,0,1,8-8.53h39.73a8.17,8.17,0,0,1,8.25,7.47A8,8,0,0,1,208,136Zm0-32H168.27A8.17,8.17,0,0,1,160,96.53,8,8,0,0,1,168,88h39.73A8.17,8.17,0,0,1,216,95.47,8,8,0,0,1,208,104Z'%3E%3C/path%3E%3C/svg%3E");
}

.navbar-icon-account {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='%23000000' viewBox='0 0 256 256'%3E%3Cpath d='M230.93,220a8,8,0,0,1-6.93,4H32a8,8,0,0,1-6.92-12c15.23-26.33,38.7-45.21,66.09-54.16a72,72,0,1,1,73.66,0c27.39,8.95,50.86,27.83,66.09,54.16A8,8,0,0,1,230.93,220Z'%3E%3C/path%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='32' height='32' fill='%23000000' viewBox='0 0 256 256'%3E%3Cpath d='M230.93,220a8,8,0,0,1-6.93,4H32a8,8,0,0,1-6.92-12c15.23-26.33,38.7-45.21,66.09-54.16a72,72,0,1,1,73.66,0c27.39,8.95,50.86,27.83,66.09,54.16A8,8,0,0,1,230.93,220Z'%3E%3C/path%3E%3C/svg%3E");
}

/* UI Label: Concourse 500 at 1rem, as approved in the specimen. */
.navbar-label {
    font-family: var(--font-family-headings);
    font-size: var(--font-size-base);
    font-weight: 500;
}

/* ==========================================================================
   Photo Grid - iOS Style

   Photos display at natural aspect ratios (4:3 landscape, 3:4 portrait)
   aligned at horizontal and vertical center lines.
   Minimal gaps between photos.
   ========================================================================== */

/* Photos are the stars, and every photo is the same length on its
   longest edge -- a landscape and a portrait must not read as different
   zoom levels.  --photo-ar is the photo's own width/height, emitted
   inline from the persisted dimensions.  Landscape has ar > 1 so min()
   picks 1 and the box is the full long side; portrait has ar < 1 so the
   box is L*ar wide, which makes it exactly L tall.  The short side falls
   out of the photo's own ratio, so nothing is cropped.

   Driven by width, not max-width: max-width only clamps, so a photo
   smaller than the cap would keep its own size and the long side would
   come out different for every photo.

   A photo with no persisted dimensions has no --photo-ar and falls back
   to 4/3, today's assumption, which object-fit: cover then crops to.
   Where the ratio IS known the box matches the photo exactly and cover
   has nothing left to crop, so one declaration serves both. */
.photo-grid-item {
    width: calc(var(--photo-long-side) * min(1, var(--photo-ar, 4/3)));
    max-width: 100%;
    margin-bottom: var(--spacing-sm);
}

/* height:auto keeps the ratio when max-width shrinks the box on a narrow
   phone, so a portrait never distorts to fit. */
.photo-grid-item img {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: var(--photo-ar, 4/3);
    object-fit: cover;
    background-color: var(--color-bg-subtle);
    border-radius: var(--radius-md);
}

/* ==========================================================================
   Timeline View

   Photos and notes organized by date sections.
   Date headers separate content.
   ========================================================================== */

.timeline {
    padding: var(--spacing-md);
    max-width: var(--content-max-width);
    margin: 0 auto;
}

.timeline-section {
    background: var(--color-surface);
    border-radius: var(--radius-md);
    padding: var(--spacing-md);
    margin-bottom: var(--spacing-md);
}

/* Layout only; typography comes from .date-heading. */
.timeline-date {
    padding: 0 0 var(--spacing-sm);
    margin-bottom: var(--spacing-sm);
}

/* Full-width stacked photos; each .photo-grid-item carries its own
   bottom margin and width cap. */
.timeline-photos {
    margin-bottom: var(--spacing-sm);
}

/* ==========================================================================
   Notes - Optimal Readability

   Line length between 50-75 characters for comfortable reading.
   ========================================================================== */

.note {
    background-color: var(--color-bg-elevated);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    padding: var(--spacing-md);
    margin: var(--spacing-sm) 0;
}

.note-content {
    max-width: var(--note-max-width);
    font-family: var(--font-family-prose);
    font-size: var(--font-size-xl); /* matches .prose */
    line-height: var(--line-height-relaxed);
    color: var(--color-text);
}

.note-content p {
    margin-bottom: var(--spacing-md);
}

.note-content p:last-child {
    margin-bottom: 0;
}

.note-meta {
    color: var(--color-text-muted); /* de-emphasized by color, not size */
    margin-top: var(--spacing-sm);
}

/* Note editor */
.note-editor {
    width: 100%;
}

.note-textarea {
    width: 100%;
    max-width: var(--note-max-width);
    min-height: 120px;
    padding: var(--spacing-md);
    font-family: var(--font-family-prose);
    font-size: var(--font-size-xl); /* matches .prose */
    line-height: var(--line-height-relaxed);
    color: var(--color-text);
    background-color: var(--color-bg);
    border: 1px solid var(--color-border);
    border-radius: var(--radius-md);
    resize: vertical;
}

.note-textarea:focus {
    outline: none;
    border-color: var(--color-primary);
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

/* ==========================================================================
   Buttons
   ========================================================================== */

.btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-sm) var(--spacing-lg);
    font-family: var(--font-family-headings);
    font-size: var(--font-size-base);
    font-weight: 500;
    line-height: 1;
    text-decoration: none;
    border: none;
    border-radius: var(--radius-lg);
    cursor: pointer;
    transition: background-color 0.15s, opacity 0.15s;
}

@media (hover: hover) {
    .btn:hover {
        text-decoration: none;
    }
}

/* Hover is gated to real pointers, so on a phone the press state is the
   only feedback a control can give. Same scale idiom .floating-add-btn
   already used. */
.btn:active:not(:disabled) {
    transform: scale(0.98);
}

.btn:disabled {
    opacity: 0.6;
    cursor: not-allowed;
}

.btn-primary {
    background-color: var(--color-primary);
    color: var(--color-text-inverse);
}

@media (hover: hover) {
    .btn-primary:hover:not(:disabled) {
        background-color: var(--color-primary-hover);
    }
}

.btn-primary:active:not(:disabled) {
    background-color: var(--color-primary-active);
}

.btn-secondary {
    background-color: transparent;
    color: var(--color-primary);
    border: 1px solid var(--color-primary);
}

@media (hover: hover) {
    .btn-secondary:hover:not(:disabled) {
        background-color: var(--teal-96);
    }
}

@media (prefers-color-scheme: dark) and (hover: hover) {
    .btn-secondary:hover:not(:disabled) {
        background-color: color-mix(in srgb, var(--teal-49) 15%, transparent);
    }
}

/* Large button - bigger tap target, same text size */
.btn-lg {
    padding: var(--spacing-md) var(--spacing-xl);
}

.btn-delete {
    background: none;
    border: none;
    color: var(--color-error);
    font-size: var(--font-size-sm);
    cursor: pointer;
    padding: var(--spacing-sm) 0;
    width: 100%;
    text-align: left;
    margin-top: var(--spacing-sm);
}

/* ==========================================================================
   Form Elements
   ========================================================================== */

.form-group {
    margin-bottom: var(--spacing-md);
}

/* Small-caps style label: uppercase Concourse, clearly distinct from
   both prose and the input's entered value. */
.form-label {
    display: block;
    font-family: var(--font-family-headings);
    font-size: var(--font-size-sm);
    font-weight: 500;
    text-transform: uppercase;
    letter-spacing: 0.05em;
    color: var(--color-text-secondary);
    margin-bottom: var(--spacing-xs);
}

.form-input {
    width: 100%;
    padding: var(--spacing-sm) var(--spacing-md);
    font-family: var(--font-family);
    font-size: var(--font-size-xl); /* entered values match body text */
    color: var(--color-text);
    background-color: var(--color-input-bg);
    border: none;
    border-radius: var(--radius-md);
    box-sizing: border-box;
    transition: box-shadow 0.15s;
}

.form-input:focus {
    outline: none;
    box-shadow: 0 0 0 3px var(--color-focus-ring);
}

.form-input::placeholder {
    color: var(--color-text-muted);
}

textarea.form-input {
    resize: none;
    line-height: var(--line-height-relaxed);
}

.form-input-date {
    width: auto;
    padding: var(--spacing-xs) var(--spacing-sm);
    font-weight: 600;
    cursor: pointer;
}

.form-input-locked {
    color: var(--color-text-muted);
    cursor: default;
    opacity: 0.7;
}

.form-input-locked:focus {
    border-color: var(--color-border);
    box-shadow: none;
}

.form-error {
    font-size: var(--font-size-sm);
    color: var(--color-error);
    min-height: calc(var(--font-size-sm) * 1.5);
    padding-top: var(--spacing-xs);
    visibility: hidden;
}

.form-error.is-visible {
    visibility: visible;
}

.form-value {
    font-size: var(--font-size-xl); /* matches body text */
    color: var(--color-text);
    padding: var(--spacing-sm) 0;
}

/* ==========================================================================
   Cards
   ========================================================================== */

.card {
    background-color: var(--color-surface);
    border: none;
    border-radius: var(--radius-md);
    box-shadow: 0 1px 2px var(--color-shadow);
    overflow: hidden;
}

.card-body {
    padding: var(--spacing-md);
}

/* A card title heads its own surface, so it is Heading S at full text
   color rather than the secondary color h3 carries as a subhead. */
.card-title {
    font-family: var(--font-family-headings);
    font-size: var(--font-size-xl);
    font-weight: 600;
    letter-spacing: 0.02em;
    line-height: var(--line-height-tight);
    color: var(--color-text);
    margin-bottom: var(--spacing-sm);
}

/* ==========================================================================
   Alerts & Messages
   ========================================================================== */

.alert {
    padding: var(--spacing-md);
    border-radius: var(--radius-md);
    font-size: var(--font-size-sm);
}

.alert-error {
    background-color: var(--color-error-bg);
    color: var(--color-error);
    border: 1px solid var(--color-error);
}

.alert-warning {
    background-color: var(--color-warning-bg);
    color: var(--yellow-27);
    border: 1px solid var(--color-warning-border);
}

@media (prefers-color-scheme: dark) {
    .alert-warning {
        color: var(--yellow-70);
    }
}

/* ==========================================================================
   Empty States
   ========================================================================== */

.empty-state {
    text-align: center;
    padding: var(--spacing-sm) var(--spacing-xl) var(--spacing-xl);
}

.empty-state p {
    max-width: var(--note-max-width);
    margin: var(--spacing-sm) auto 0;
}

.empty-state-arrow {
    margin: 0 auto var(--spacing-xs);
    width: 100px;
    position: relative;
    left: 30%;
    transform: rotate(var(--dynamic-angle, 1deg));
    transition: transform 0.1s linear;
}

/* ==========================================================================
   Logged-In Home Page
   ========================================================================== */

.logged-in-home {
    width: 100%;
    max-width: 65ch;
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--spacing-md);
    padding: var(--spacing-md);
}

.logged-in-home .welcome {
    color: var(--color-text);
    margin-bottom: var(--spacing-sm);
}

.trial-message {
    margin: var(--spacing-md) 0;
}

.trial-status-secondary {
    font-size: 0.85rem;
    color: var(--color-text-muted);
    margin-top: var(--spacing-md);
    text-align: center;
}

.logged-in-home .photo-count {
    font-size: var(--font-size-base);
    color: var(--color-text-secondary);
    margin-bottom: var(--spacing-sm);
}

/* ==========================================================================
   Loading & Processing States
   ========================================================================== */

.shimmer {
    background: linear-gradient(
        90deg,
        var(--color-bg-subtle) 0%,
        var(--color-bg-elevated) 50%,
        var(--color-bg-subtle) 100%
    );
    background-size: 200% 100%;
    animation: shimmer 1.5s infinite;
}

@keyframes shimmer {
    0% { background-position: 200% 0; }
    100% { background-position: -200% 0; }
}

@keyframes spin {
    to { transform: rotate(360deg); }
}

/* Uploading: the label collapses and the spinner takes its place.
   No colour override - primary is reserved for committing an edit, and
   adding photos is not that. */
.header-add-btn.uploading {
    font-size: 0;
    cursor: default;
    pointer-events: none;
}

.header-add-btn.uploading::after {
    content: '';
    display: block;
    width: 22px;
    height: 22px;
    border: 2.5px solid color-mix(in srgb, var(--color-primary) 40%, transparent);
    border-top-color: var(--color-primary);
    border-radius: 50%;
    animation: spin 0.7s linear infinite;
}

.processing-indicator {
    display: flex;
    align-items: center;
    justify-content: center;
    gap: var(--spacing-sm);
    color: var(--color-primary);
    font-size: var(--font-size-sm);
}

/* ==========================================================================
   Review Mode Specific
   ========================================================================== */

.review-day-nav {
    display: flex;
    align-items: center;
    justify-content: space-between;
    padding: var(--spacing-md);
    border-bottom: 1px solid var(--color-border);
}

.review-day-date {
    font-size: var(--font-size-xl);
    font-weight: 600;
    color: var(--color-text);
}

.review-nav-btn {
    width: 44px;
    height: 44px;
    display: flex;
    align-items: center;
    justify-content: center;
    background: none;
    border: none;
    color: var(--color-primary);
    font-size: var(--font-size-xl);
    cursor: pointer;
    border-radius: var(--radius-md);
}

@media (hover: hover) {
    .review-nav-btn:hover {
        background-color: var(--color-bg-subtle);
    }
}

.review-nav-btn:disabled {
    opacity: 0.3;
    cursor: not-allowed;
}

.review-photos {
    padding: var(--spacing-md);
}

.review-note {
    padding: var(--spacing-md);
    border-top: 1px solid var(--color-border);
}

/* ==========================================================================
   Fullscreen Photo View (Progressive Disclosure)
   ========================================================================== */

.fullscreen-photo {
    position: fixed;
    inset: 0;
    background-color: var(--color-backdrop);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
}

.fullscreen-photo img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
}

.fullscreen-photo-counter {
    position: absolute;
    top: var(--spacing-md);
    left: 50%;
    transform: translateX(-50%);
    color: var(--color-text-inverse);
    font-size: var(--font-size-sm);
    background-color: var(--color-overlay-badge);
    padding: var(--spacing-xs) var(--spacing-sm);
    border-radius: var(--radius-sm);
}

.fullscreen-photo-close {
    position: absolute;
    top: var(--spacing-md);
    right: var(--spacing-md);
    color: var(--color-text-inverse);
    font-size: var(--font-size-2xl);
    background: none;
    border: none;
    cursor: pointer;
    padding: var(--spacing-sm);
}

/* Full-screen photo viewer with swipe navigation */
.fullscreen-viewer {
    position: fixed;
    inset: 0;
    background-color: var(--color-backdrop);
    z-index: 200;
    display: flex;
    align-items: center;
    justify-content: center;
    touch-action: pinch-zoom;
}

.fullscreen-viewer img {
    max-width: 100%;
    max-height: 100%;
    object-fit: contain;
    user-select: none;
    -webkit-user-drag: none;
}

.fullscreen-photo-date {
    position: absolute;
    bottom: 20px;
    left: 50%;
    transform: translateX(-50%);
    color: var(--color-text-inverse);
    font-size: var(--font-size-sm);
    background: var(--color-overlay);
    padding: var(--spacing-xs) var(--spacing-md);
    border-radius: 20px;
    text-transform: uppercase;
    letter-spacing: 0.05em;
}

/* ==========================================================================
   Celebration / Achievement Messages
   ========================================================================== */

.celebration {
    text-align: center;
    padding: var(--spacing-xl);
    max-width: 400px;
    margin: 0 auto;
}

.celebration-icon {
    font-size: 4rem;
    margin-bottom: var(--spacing-md);
}

/* Heading typography comes from the h1 element style. */
.celebration-title {
    color: var(--color-text);
    margin-bottom: var(--spacing-sm);
}

.celebration-message {
    color: var(--color-text-secondary);
    font-size: var(--font-size-base);
    line-height: var(--line-height-relaxed);
    margin-bottom: var(--spacing-lg);
}

/* ==========================================================================
   Utility Classes
   ========================================================================== */

.visually-hidden {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

.text-center { text-align: center; }
.text-left { text-align: left; }
.text-right { text-align: right; }

.text-primary { color: var(--color-primary); }
.text-secondary { color: var(--color-text-secondary); }
.text-muted { color: var(--color-text-muted); }
.text-error { color: var(--color-error); }

.mt-0 { margin-top: 0; }
.mt-sm { margin-top: var(--spacing-sm); }
.mt-md { margin-top: var(--spacing-md); }
.mt-lg { margin-top: var(--spacing-lg); }
.mt-xl { margin-top: var(--spacing-xl); }

.mb-0 { margin-bottom: 0; }
.mb-sm { margin-bottom: var(--spacing-sm); }
.mb-md { margin-bottom: var(--spacing-md); }
.mb-lg { margin-bottom: var(--spacing-lg); }
.mb-xl { margin-bottom: var(--spacing-xl); }

/* Hide file inputs but keep accessible */
input[type="file"] {
    position: absolute;
    width: 1px;
    height: 1px;
    padding: 0;
    margin: -1px;
    overflow: hidden;
    clip: rect(0, 0, 0, 0);
    white-space: nowrap;
    border: 0;
}

/* ==========================================================================
   Timeline Notes
   ========================================================================== */

.timeline-notes {
    margin-top: var(--spacing-sm);
}

.note-entry {
    border-radius: var(--radius-md);
    padding: var(--spacing-sm) var(--spacing-md);
    margin: var(--spacing-xs) 0;
    cursor: pointer;
}

@media (hover: hover) {
    .note-entry:hover {
        background: var(--color-border-subtle);
    }
}

/* A note in the stream is reading text: it keeps the Prose size set by
   .note-content and is never shrunk.  The time is de-emphasized by
   color, like .note-meta. */
.note-entry .note-content {
    color: var(--color-text);
    line-height: var(--line-height-relaxed);
    white-space: pre-wrap;
}

.note-entry .note-time {
    color: var(--color-text-muted);
    margin-top: var(--spacing-xs);
}

/* ==========================================================================
   Floating Add Button
   ========================================================================== */

.floating-add-btn {
    position: fixed;
    bottom: calc(var(--tab-bar-height) + 16px);
    right: 16px;
    width: 56px;
    height: 56px;
    border-radius: 50%;
    background: var(--color-primary);
    color: var(--color-text-inverse);
    font-size: 28px;
    font-weight: 300;
    border: none;
    box-shadow: 0 4px 12px var(--color-shadow);
    cursor: pointer;
    z-index: 90;
    transition: background-color 0.15s, transform 0.15s;
}

@media (hover: hover) {
    .floating-add-btn:hover {
        background: var(--color-primary-hover);
    }
}

.floating-add-btn:active {
    transform: scale(0.95);
}

/* ==========================================================================
   Add Menu
   ========================================================================== */

.add-menu {
    position: absolute;
    top: 100%;
    right: 0;
    margin-top: 4px;
    background: var(--color-bg-elevated);
    border-radius: var(--radius-lg);
    box-shadow: 0 4px 16px var(--color-shadow-heavy);
    z-index: 90;
    overflow: hidden;
}

.add-menu.hidden {
    display: none;
}

.add-menu-item {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
    padding: var(--spacing-md) var(--spacing-lg);
    width: 100%;
    border: none;
    background: none;
    font-family: var(--font-family);
    font-size: var(--font-size-base);
    color: var(--color-text);
    cursor: pointer;
    transition: background-color 0.15s;
}

@media (hover: hover) {
    .add-menu-item:hover {
        background: var(--color-bg-subtle);
    }
}

.add-menu-item:not(:last-child) {
    border-bottom: 1px solid var(--color-border-subtle);
}

.add-menu-icon {
    display: inline-block;
    width: 20px;
    height: 20px;
    background-color: currentColor;
    -webkit-mask-size: contain;
    mask-size: contain;
    -webkit-mask-repeat: no-repeat;
    mask-repeat: no-repeat;
    -webkit-mask-position: center;
    mask-position: center;
}

.add-menu-icon-photo {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Crect width='256' height='256' fill='none'/%3E%3Crect x='72' y='40' width='144' height='144' rx='8' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3Ccircle cx='120' cy='88' r='16' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3Cpath d='M184,184v24a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V80a8,8,0,0,1,8-8H72' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3Cpath d='M96.69,184l77.65-77.66a8,8,0,0,1,11.32,0L216,136.69' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Crect width='256' height='256' fill='none'/%3E%3Crect x='72' y='40' width='144' height='144' rx='8' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3Ccircle cx='120' cy='88' r='16' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3Cpath d='M184,184v24a8,8,0,0,1-8,8H48a8,8,0,0,1-8-8V80a8,8,0,0,1,8-8H72' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3Cpath d='M96.69,184l77.65-77.66a8,8,0,0,1,11.32,0L216,136.69' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3C/svg%3E");
}

.add-menu-icon-note {
    -webkit-mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Crect width='256' height='256' fill='none'/%3E%3Cpath d='M92.69,216H48a8,8,0,0,1-8-8V163.31a8,8,0,0,1,2.34-5.65L165.66,34.34a8,8,0,0,1,11.31,0L221.66,79a8,8,0,0,1,0,11.31L98.34,213.66A8,8,0,0,1,92.69,216Z' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3Cline x1='136' y1='64' x2='192' y2='120' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3C/svg%3E");
    mask-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 256 256'%3E%3Crect width='256' height='256' fill='none'/%3E%3Cpath d='M92.69,216H48a8,8,0,0,1-8-8V163.31a8,8,0,0,1,2.34-5.65L165.66,34.34a8,8,0,0,1,11.31,0L221.66,79a8,8,0,0,1,0,11.31L98.34,213.66A8,8,0,0,1,92.69,216Z' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3Cline x1='136' y1='64' x2='192' y2='120' fill='none' stroke='%23000' stroke-linecap='round' stroke-linejoin='round' stroke-width='12'/%3E%3C/svg%3E");
}

/* ==========================================================================
   Modal
   ========================================================================== */

.modal {
    position: fixed;
    inset: 0;
    background: var(--color-overlay);
    display: flex;
    align-items: flex-end;
    z-index: 200;
}

.modal.hidden {
    display: none;
}

.modal-content {
    background: var(--color-bg-elevated);
    width: 100%;
    border-radius: var(--radius-lg) var(--radius-lg) 0 0;
    padding: var(--spacing-md);
    max-height: 80vh;
    overflow-y: auto;
}

.modal-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: var(--spacing-md);
}

.modal-close {
    background: none;
    border: none;
    font-size: 1.5rem;
    line-height: 1;
    cursor: pointer;
    color: var(--color-text-muted);
    padding: var(--spacing-xs);
}

@media (hover: hover) {
    .modal-close:hover {
        color: var(--color-text);
    }
}

.modal-actions {
    margin-top: var(--spacing-md);
    text-align: right;
}

.date-label-locked::after {
    content: " (from photo)";
    font-size: var(--font-size-sm);
}

/* Desktop: center modal */
@media (min-width: 768px) {
    .modal {
        align-items: center;
        justify-content: center;
    }

    .modal-content {
        max-width: 500px;
        border-radius: var(--radius-lg);
    }
}

/* ==========================================================================
   Margin Notes

   On wide screens, the figure floats to the right of the paragraph as a
   margin note.  On narrow screens it wraps below the paragraph.
   ========================================================================== */

.margin-note-container {
    max-width: calc(var(--note-max-width) + 220px);
}

.margin-note {
    margin-top: var(--spacing-md);
}

.margin-note img {
    border-radius: var(--radius-md);
    width: 100%;
    height: auto;
    max-width: 300px;
}

.margin-note figcaption {
    font-size: var(--font-size-xs);
    color: var(--color-text-muted);
    line-height: var(--line-height-normal);
    margin-top: var(--spacing-xs);
    max-width: 300px;
}

@media (min-width: 900px) {
    .margin-note-container {
        position: relative;
    }

    .margin-note {
        position: absolute;
        top: 0;
        left: calc(var(--note-max-width) + var(--spacing-lg));
        width: 200px;
        margin-top: 0;
    }

    .margin-note img {
        max-width: 200px;
    }

    .margin-note figcaption {
        max-width: 200px;
    }
}

/* ==========================================================================
   Page Navigation (content inside .navbar for About, Pricing, Privacy, Terms)
   ========================================================================== */

.page-nav {
    justify-content: space-between;
}

main:has(+ .navbar) {
    padding-bottom: var(--tab-bar-height);
}

.page-nav a {
    display: inline-flex;
    align-items: center;
    gap: var(--spacing-xs);
    color: var(--color-nav-inactive);
    text-decoration: none;
}

@media (hover: hover) {
    .page-nav a:hover {
        color: var(--color-nav-inactive-hover);
    }
}

.page-nav-prev,
.page-nav-next {
    min-width: 0;
    flex: 1;
}

.page-nav-prev {
    text-align: left;
}

.page-nav-next {
    text-align: right;
    justify-content: flex-end;
}

.page-nav-home {
    flex: 0 0 auto;
}

.page-nav-home .navbar-icon {
    width: 20px;
    height: 20px;
}

/* ==========================================================================
   Photo-note card (journal timeline)
   ========================================================================== */

/* Captioned photo: same size as any other photo, caption as prose
   below it. No card box — photos are the stars. */
.photo-note-card {
    cursor: pointer;
    margin-bottom: var(--spacing-md);
    width: calc(var(--photo-long-side) * min(1, var(--photo-ar, 4/3)));
    max-width: 100%;
}

.photo-note-img {
    width: 100%;
    height: auto;
    display: block;
    aspect-ratio: var(--photo-ar, 4/3);
    object-fit: cover;
    border-radius: var(--radius-md);
}

.photo-note-text {
    padding: var(--spacing-xs) 0 0;
    font-family: var(--font-family-prose);
    line-height: var(--line-height-relaxed);
    color: var(--color-text);
}

/* ==========================================================================
   Photo modal — redesigned two-column layout
   ========================================================================== */

.photo-modal-body {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-md);
}

.photo-modal-image-col img.photo-modal-img {
    width: 100%;
    max-height: 50vh;
    object-fit: contain;
    border-radius: var(--radius-md);
}

.photo-modal-fields-col {
    display: flex;
    flex-direction: column;
    gap: var(--spacing-sm);
}

.photo-modal-actions {
    display: flex;
    justify-content: space-between;
    gap: var(--spacing-sm);
    margin-top: auto;
}

/* Danger button variant */

.btn-danger {
    background-color: var(--color-danger);
    color: var(--color-text-inverse);
}

@media (hover: hover) {
    .btn-danger:hover:not(:disabled) {
        opacity: 0.85;
    }
}

/* Desktop two-column photo modal */

@media (min-width: 768px) {
    .photo-modal-content {
        max-width: 800px;
    }

    .photo-modal-body {
        flex-direction: row;
        align-items: stretch;
        min-height: 300px;
    }

    .photo-modal-image-col {
        flex: 1;
        display: flex;
        align-items: flex-start;
    }

    .photo-modal-image-col img.photo-modal-img {
        max-height: 60vh;
    }

    .photo-modal-fields-col {
        flex: 1;
    }
}

/* =====================================================================
 * Action controls - the standard set
 *
 * Shape comes from a fill lighter than the page, never from a border.
 * Glyphs are reserved for the two that read the same anywhere: back and
 * check. Everything else uses its word. The primary colour marks
 * committing an edit and nothing else.
 *
 * The action bar - back left, check right - belongs at the top of any
 * screen that is cancellable and committable. A base screen (Journal,
 * Account) has nothing to commit and carries no action bar; its actions
 * live in the bottom nav.
 * ===================================================================== */
.action-bar {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

/* The chip takes its shape from a stroke, not a fill. --color-surface
   on --color-bg is 1.06:1, and on a .card (also --color-surface) exactly
   1:1, so a filled chip was invisible; every palette fill that clears
   3:1 is dark enough to turn Edit into a solid button louder than the
   commit. 48px, not the 44px minimum: a bordered control cannot buy tap
   area with invisible padding, because the stroke is the promise, so hit
   region and drawn box must match. 3rem in the 56px header leaves 4px of
   slack. Approved 2026-08-24. */
.action-btn {
    width: 3rem;
    height: 3rem;
    padding: 0;
    border: 1px solid var(--color-border-control);
    border-radius: 50%;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    font-family: var(--font-family-headings);
    font-size: 1.5rem;
    line-height: 1;
    background: var(--color-surface);
    color: var(--color-text);
    cursor: pointer;
    transition: border-color 0.15s, background-color 0.15s, box-shadow 0.15s;
}

/* The chevron sits high in its em box; nudge it back to centre. */
.action-btn-back {
    padding-bottom: 0.18em;
    text-decoration: none;
}

/* A word needs width, so it is a pill: same height and stroke, wider
   box. min-width keeps a short label from collapsing into a chip that is
   tall but thin, which the horizontal approach would punish. */
.action-btn-pill {
    width: auto;
    min-width: 3rem;
    border-radius: 999px;
    padding: 0 var(--spacing-md);
    font-size: var(--font-size-base);
    font-weight: 500;
}

.action-btn-primary {
    font-size: 1.15rem;
    background: var(--color-primary);
    color: var(--color-text-inverse);
    border-color: transparent;
}

/* Destructive actions are marked with red ink, never a red fill: the
   same signal survives on top of a photo, where a fill would fight the
   image, and it keeps the rarest action from being the loudest thing on
   the page. It takes the same stroke every other action button has, so
   there is still one pill style. Red marks, grey means marked, primary
   commits — three colours, three jobs. */
.action-btn-danger {
    color: var(--color-danger);
    border-color: var(--color-danger);
}

.action-btn:disabled {
    opacity: 0.5;
    cursor: default;
}

/* Hover strengthens the stroke — by weight first, colour second. Colour
   alone could not carry it: the rest-to-hover step is the same size in
   both schemes (2.69:1 light, 2.79:1 dark), but in dark the stroke moves
   toward near-white and away from the page, while in light a 1px line
   going dark-to-darker on a pale page saturates and reads as the same
   line. Doubling the line is scheme-symmetric.

   box-shadow, not border-width: growing the border would grow the 48px
   box and shove every chip beside it. Approved 2026-08-24. */
@media (hover: hover) {
    .action-btn:hover:not(:disabled) {
        border-color: currentColor;
        box-shadow: 0 0 0 1px currentColor;
    }
    /* Primary is a fill, not a stroke: nothing to thicken, so it steps
       its fill down exactly as .btn-primary does. */
    .action-btn-primary:hover:not(:disabled) {
        background-color: var(--color-primary-hover);
        border-color: transparent;
        box-shadow: none;
    }
    /* Danger keeps its red ink and thickens its own red stroke; without
       this it would hover to currentColor like any neutral chip. */
    .action-btn-danger:hover:not(:disabled) {
        border-color: var(--color-danger);
        box-shadow: 0 0 0 1px var(--color-danger);
    }
}

.action-btn:active:not(:disabled) {
    transform: scale(0.95);
}

/* =====================================================================
 * Entry edit page (full-screen photo edit, replaces the photo modal)
 * ===================================================================== */
.entry-edit-header {
    background-color: var(--color-bg);
}

.entry-edit-header .header-inner {
    display: flex;
    align-items: center;
    justify-content: space-between;
}

.entry-edit-actions {
    display: flex;
    align-items: center;
    gap: var(--spacing-sm);
}

/* Resting keeps the word; editing commits, so it takes the primary
   fill. entry-edit.js swaps the classes with the label. */
.entry-edit-toggle.is-edit {
    width: auto;
    border-radius: 999px;
    padding: 0 var(--spacing-md);
    font-size: var(--font-size-sm);
}

.entry-edit-toggle.is-save {
    font-size: 1.15rem;
    background: var(--color-primary);
    color: var(--color-text-inverse);
}

/* The share glyph ships at 22px; ease it down inside the circle. */
.entry-share-btn svg {
    width: 18px;
    height: 18px;
}

/* Nothing gets shared mid-edit: the link would publish a state the
   author has not committed. */
.entry-edit-page.editing .entry-share-btn {
    display: none;
}

.entry-edit-content {
    display: flex;
    flex-direction: column;
    padding-top: var(--header-height);
}

/* Same long side as a journal or story photo, sized from the persisted
   dimensions the same way (see .photo-grid-item for the derivation).
   The box is the photo's own shape, so there is no letterboxed band
   inside it and the delete X can sit on the photo's real corner. */
.entry-photo-wrap {
    position: relative;
    width: calc(var(--photo-long-side) * min(1, var(--photo-ar, 4/3)));
    max-width: calc(100% - var(--spacing-md) * 2);
    margin: var(--spacing-sm) auto 0;
}

/* height:auto keeps the ratio when max-width shrinks the box on a narrow
   phone. No max-height: the long side is the cap, the same one every
   other photo obeys.

   No 4/3 fallback, as on .story-photo: with no --photo-ar this resolves
   to auto, the image's own ratio. An entry photo is never cropped, and
   guessing a ratio for one would start. */
.entry-photo {
    display: block;
    width: 100%;
    height: auto;
    aspect-ratio: var(--photo-ar);
    border-radius: var(--radius-md);
}

/* The box edge is the photo edge, so one inset off the corner serves
   both orientations. The mark inside is an SVG, so the chip centres it
   rather than relying on a text glyph to centre itself. */
.entry-delete-x {
    display: none;
    align-items: center;
    justify-content: center;
    position: absolute;
    top: var(--spacing-sm);
    right: var(--spacing-sm);
    z-index: 2;
    width: 1.75rem;
    height: 1.75rem;
    border-radius: 50%;
    border: none;
    background: color-mix(in srgb, var(--yellow-96) 82%, transparent);
    color: var(--color-danger);
    line-height: 1;
    cursor: pointer;
}

.entry-edit-page.editing .entry-delete-x {
    display: inline-flex;
}

/* Red on the glyph, never on the chip. The scrim is what keeps this
   control legible over any photo at all, and a red fill there would
   fight the image. --color-danger on the pale scrim is 5.95 and on the
   dark scrim 5.04; a glyph is a graphical object, so it needs 3:1 rather
   than the 4.5:1 a word would. Approved 2026-08-24. */

/* Once staged, the control sheds the red: red marks what a tap will
   destroy, and this tap now restores. Red marks, grey means marked. */
.entry-photo-wrap.is-staged-delete .entry-delete-x {
    color: var(--color-text);
}

/* Delete is staged, not done: the trash can marks the photo and the
   check in the header is what actually destroys it. Drained of colour and dropped to a
   quarter opacity, the photo reads as already gone while staying the
   thing you can put back. Approved 2026-08-23. */
.entry-photo-wrap.is-staged-delete .entry-photo {
    opacity: 0.25;
    filter: grayscale(1);
    transition: opacity 0.15s, filter 0.15s;
}

/* The pale scrim would vanish over a dark photo; invert it. */
@media (prefers-color-scheme: dark) {
    .entry-delete-x {
        background: color-mix(in srgb, var(--grey-23) 78%, transparent);
    }
}

.entry-edit-fields {
    padding: var(--spacing-md) var(--spacing-lg);
    display: flex;
    flex-direction: column;
    gap: var(--spacing-sm);
}

.entry-date {
    color: var(--color-text-secondary);
    font-size: 0.85rem;
    letter-spacing: 0.05em;
}

.entry-caption {
    white-space: pre-wrap;
    color: var(--color-text);
    min-height: 1.5rem;
}

.entry-caption:empty::before {
    content: "No note yet. Tap Edit to add one.";
    color: var(--color-text-muted);
}

.entry-toast {
    position: fixed;
    bottom: 5rem;
    left: 50%;
    transform: translateX(-50%);
    background: var(--color-overlay-badge);
    color: var(--color-text-inverse);
    padding: var(--spacing-sm) var(--spacing-md);
    border-radius: 6px;
    z-index: 300;
    max-width: 90vw;
    overflow-wrap: anywhere;
}

.handle-preview {
    color: var(--color-text-secondary);
    font-weight: 600;
    margin: var(--spacing-sm) 0 var(--spacing-xs);
    min-height: 1.2rem;
    overflow-wrap: anywhere;
}

.handle-modal-hint,
.handle-modal-rules {
    color: var(--color-text-secondary);
    font-size: 0.85rem;
    margin: var(--spacing-xs) 0;
}

@media (min-width: 768px) {
    .entry-edit-content {
        max-width: 768px;
        margin: 0 auto;
        width: 100%;
    }
}

/* Account page shares list */
.share-list {
    list-style: none;
    padding: 0;
    margin: 0;
    display: flex;
    flex-direction: column;
    gap: var(--spacing-sm);
}

.share-list-item {
    display: flex;
    align-items: baseline;
    gap: var(--spacing-md);
}

.share-date {
    color: var(--color-text-secondary);
    white-space: nowrap;
}

.share-link {
    color: var(--color-link);
    overflow-wrap: anywhere;
}

.share-status {
    margin-left: auto;
    color: var(--color-text-muted);
}

.share-list-empty {
    color: var(--color-text-muted);
}

/* A staged share row dims and drains exactly as a staged photo does, so
   the feedback is one vocabulary even though the control that stages it
   is a word on a row and a glyph on a photo. The control itself stays at
   full strength — it is the one that puts the row back. */
.share-list-item.is-staged-delete > :not(.action-btn) {
    opacity: 0.25;
    filter: grayscale(1);
    transition: opacity 0.15s, filter 0.15s;
}

/* ==========================================================================
   Prose

   Reading text (paragraphs someone settles in to read) gets the serif,
   a measure cap, and relaxed leading.  Apply to content-page article
   bodies and any long-form text block.  See docs/style-guide.md.
   ========================================================================== */

.prose {
    max-width: var(--measure);
    font-family: var(--font-family-prose);
    font-size: var(--font-size-xl); /* Equity runs small; 1rem reads too small */
    line-height: var(--line-height-relaxed);
}

.prose p {
    margin-bottom: var(--spacing-md);
}

.prose p:last-child {
    margin-bottom: 0;
}

/* ==========================================================================
   Story Page

   Publicly shared single-photo story.  Centered reading column: title,
   photo, date, prose, byline, quiet wordmark footer.  Visitors arrive
   cold from shared links, so keep it simple and fast.
   ========================================================================== */

.page-story {
    display: flex;
    flex-direction: column;
    align-items: center;
    min-height: 100vh;
    min-height: 100dvh;
    padding: var(--spacing-lg) var(--spacing-md);
}

.story-page {
    width: 100%;
    max-width: var(--measure);
    display: flex;
    flex-direction: column;
}

.story-title {
    margin: 0 0 var(--spacing-xs); /* date subtitle sits directly below */
}

/* Same long side as a journal photo, so a portrait story photo is as
   tall as a landscape one is wide. */
.story-photo-wrap {
    width: calc(var(--photo-long-side) * min(1, var(--photo-ar, 4/3)));
    max-width: 100%;
    margin: 0 0 var(--spacing-md);
}

/* No 4/3 fallback here on purpose: with no --photo-ar this resolves to
   auto, i.e. the image's own ratio. A story photo has never been
   cropped, and guessing a ratio for one would start. */
.story-photo {
    width: 100%;
    height: auto;
    aspect-ratio: var(--photo-ar);
    border-radius: var(--radius-md);
}

/* Layout only; typography comes from .date-heading. */
.story-date {
    margin-bottom: var(--spacing-md);
}

/* Story prose is just .prose; this only spaces it above the byline. */
.story-page .prose {
    margin-bottom: var(--spacing-md);
}

.story-byline {
    color: var(--color-text-secondary); /* prose, de-emphasized by color */
}

.story-footer {
    width: 100%;
    max-width: var(--measure);
    margin-top: auto;
    padding-top: var(--spacing-xl);
}

/* A standard link (color + underline) with a separator border. */
.story-wordmark {
    display: inline-block;
    padding-top: var(--spacing-md);
    border-top: 1px solid var(--color-border-subtle);
    color: var(--color-link);
    text-decoration: underline;
}

@media (hover: hover) {
    .story-wordmark:hover {
        color: var(--color-link-hover);
    }
}

/* ==========================================================================
   Reduced motion
   ========================================================================== */

@media (prefers-reduced-motion: reduce) {
    .btn:active:not(:disabled),
    .action-btn:active:not(:disabled),
    .floating-add-btn:active {
        transform: none;
    }
}
