/* ==========================================================================
   photosharing.css - the page sheet for the 作品發表 channel page
   (Modules\PhotoSharing — it was the standalone public/photosharing.php).

   A template page never links storefront.css (its rules are scoped to `.sf`
   while the template body is `.shell`), and shell.css ships only the shell
   frame plus the .ui-* primitives. So this sheet carries three layers:

   1. COMPONENT LAYER - the /shop components this page RENDERS, copied
      VERBATIM from assets/css/storefront.css. Re-sync the layer by re-copying
      the block; never hand-edit it, or the two copies drift.
          section  8  Buttons                    .sf-btn / --outline
          section 10  Product card (grid only)   .sf-grid / --4
          section 11  Sections                   .sf-section + __head/__title/__more
          section 13  promo banner               .sf-promobar + its parts
          section 14  Category tiles             .sf-tiles / .sf-tile*
          section 15  Page head                  .sf-page-head / __title / __sub
          section 20  Pagination                 .sf-pagination / .sf-page-link*
    Their rhythm margins need no work: shell.css zeroes default child margins
    with `:where(.shell__view > *)`, which cannot override this sheet's
    component margins. The pager's margin-TOP is not covered by that rule, so
    it is zeroed below.

   2. THE FRAME'S SEGMENTED CONTROL, renamed .ph-seg / .ph-seg__btn - the
      demo's `.users__seg` belongs to the demo's own frame, which the template
      replaces. Its --u-* tokens are mapped to their shell twins
      (--u-hair -> --shell-hair, --u-ease -> --sf-ease, --u-glow-1 ->
      --shell-glow-1, --u-fs-2 -> 14px).

   3. PAGE-LOCAL ADDITIONS - the demo's own layer, kept with its reasoning.
      The photo strip reads each picture's SHAPE from the image itself: --ar is
      derived in the page script and never written by hand.

   THE SHEET ORDER MATTERS. This sheet loads BEFORE shell.css, so an
   equal-specificity tie goes to shell.css - which is why the two rules at the
   very end carry an extra class.
   ========================================================================== */


/* ==========================================================================
   1. COMPONENT LAYER - copied verbatim from storefront.css
   ========================================================================== */
/* -------------------------------------------------------------------------
   8. Buttons
   ------------------------------------------------------------------------- */
.sf-btn {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    gap: var(--sf-sp-2);
    padding: 11px var(--sf-sp-6);
    border: 1px solid transparent;
    border-radius: 10px;
    background: var(--sf-surface-2);
    color: var(--sf-ink);
    font-family: inherit;
    font-size: 15px;
    font-weight: 600;
    line-height: 1.2;
    cursor: pointer;
    text-align: center;
    transition: background .18s var(--sf-ease), color .18s var(--sf-ease),
        border-color .18s var(--sf-ease), transform .18s var(--sf-ease);
}

.sf-btn:active:not(:disabled) {
    transform: translateY(1px);
}

.sf-btn:disabled {
    opacity: .55;
    cursor: not-allowed;
}

.sf-btn--primary {
    background: var(--sf-brand);
    border-color: var(--sf-brand);
    color: #fff;
    box-shadow: 0 4px 14px rgba(18, 159, 229, .3);
}

.sf-btn--primary:hover:not(:disabled) {
    background: var(--sf-brand-dark);
    border-color: var(--sf-brand-dark);
    color: #fff;
}

/* Buy CTA — warm orange so the purchase action pops against the blue brand. */
.sf-btn--buy {
    background: linear-gradient(135deg, #ff8a3d, var(--sf-buy));
    border-color: var(--sf-buy);
    color: #fff;
    box-shadow: 0 4px 14px rgba(255, 106, 0, .3);
}

.sf-btn--buy:hover:not(:disabled) {
    background: linear-gradient(135deg, #ff7a2e, var(--sf-buy-dark));
    border-color: var(--sf-buy-dark);
    color: #fff;
}

.sf-btn--outline {
    background: #fff;
    border-color: var(--sf-line-strong);
    color: var(--sf-ink-2);
}

.sf-btn--outline:hover:not(:disabled) {
    border-color: var(--sf-brand);
    color: var(--sf-brand-dark);
}

.sf-btn--dark {
    background: var(--sf-ink);
    border-color: var(--sf-ink);
    color: #fff;
}

.sf-btn--dark:hover:not(:disabled) {
    background: #000;
    color: #fff;
}

.sf-btn--ghost {
    background: transparent;
    border-color: transparent;
    color: var(--sf-muted);
}

.sf-btn--ghost:hover:not(:disabled) {
    color: var(--sf-brand-dark);
    background: var(--sf-brand-soft);
}

.sf-btn--block {
    width: 100%;
}

.sf-btn--sm {
    padding: 9px var(--sf-sp-4);
    font-size: 14px;
    border-radius: 9px;
}

.sf-btn--lg {
    padding: 15px var(--sf-sp-8);
    font-size: 16px;
}


.sf-grid {
    display: grid;
    gap: var(--sf-sp-5);
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

/*
 * The 3-up step is at 768px, NOT 640px — do not lower it.
 *
 * Below 768px the container's inline padding is 16px, so a 640px viewport has
 * a 608px content box and three cards with a 20px gap would be ~189px each.
 * That is the same ~180px card size this file already rejected once: the
 * 1280px note below says 5-up at 1024px came "too early" for exactly that
 * reason. At 768px the padding steps up to 24px, giving a 720px content box
 * and ~227px cards — the same size the desktop 5-up lands on at 1280px.
 *
 * Any 2 -> 3 step shrinks cards; what matters is where it lands. Landing on a
 * size the rest of the system already uses (227px) beats landing on one it
 * has rejected (189px).
 */
@media (min-width: 768px) {
    .sf-grid {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (min-width: 1024px) {
    .sf-grid {
        grid-template-columns: repeat(4, minmax(0, 1fr));
        gap: var(--sf-sp-6);
    }
}

/* Five-up only once the container is actually wide (≥1280px) — at 1024px the
   container is still ~976px, so five cards would be ~180px each. */
@media (min-width: 1280px) {
    .sf-grid {
        grid-template-columns: repeat(5, minmax(0, 1fr));
        gap: var(--sf-sp-6);
    }
}

.sf-grid--4 {
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

/* 768px, not 640px — same reason as `.sf-grid` above. */
@media (min-width: 768px) {
    .sf-grid--4 {
        grid-template-columns: repeat(3, minmax(0, 1fr));
    }
}

@media (min-width: 1024px) {
    .sf-grid--4 {
        grid-template-columns: repeat(4, minmax(0, 1fr));
    }
}


/* -------------------------------------------------------------------------
   11. Sections
   ------------------------------------------------------------------------- */
/* Sections are no longer framed boxes — separation comes from whitespace. */
.sf-section {
    margin-bottom: var(--sf-sp-10);
}

@media (min-width: 768px) {
    .sf-section {
        margin-bottom: var(--sf-sp-12);
    }
}

.sf-section--bare {
    background: transparent;
    border: 0;
    padding: 0;
}

.sf-section__head {
    display: flex;
    align-items: center;
    justify-content: space-between;
    gap: var(--sf-sp-4);
    margin-bottom: var(--sf-sp-6);
    padding-bottom: var(--sf-sp-4);
    border-bottom: 1px solid var(--sf-line);
}

.sf-section__title {
    display: inline-flex;
    align-items: center;
    gap: var(--sf-sp-3);
    font-size: 20px;
    font-weight: 800;
    letter-spacing: -.02em;
}

.sf-section__title::before {
    content: '';
    width: 4px;
    height: 20px;
    border-radius: 2px;
    background: var(--sf-brand);
}

.sf-section__more {
    display: inline-flex;
    align-items: center;
    gap: 6px;
    font-size: 14px;
    font-weight: 600;
    color: var(--sf-muted);
    transition: color .18s var(--sf-ease);
}

.sf-section__more:hover {
    color: var(--sf-brand);
}


.sf-promobar {
    position: relative;
    display: grid;
    grid-template-columns: minmax(0, 1fr);
    gap: var(--sf-sp-4);
    align-items: center;
    margin-bottom: var(--sf-sp-6);
    padding: var(--sf-sp-5);
    border-radius: var(--sf-radius-lg);
    background: linear-gradient(120deg, var(--sf-brand-deep) 0%, var(--sf-brand) 55%, #5cc8f7 100%);
    box-shadow: var(--sf-shadow-sm);
    color: #fff;
    overflow: hidden;
}

/* Keep the copy column shrinkable beside the action column. */
.sf-promobar__body {
    min-width: 0;
}

@media (min-width: 768px) {
    .sf-promobar {
        grid-template-columns: minmax(0, 1fr) auto;
        gap: var(--sf-sp-6);
        padding: var(--sf-sp-6) var(--sf-sp-8);
    }
}

.sf-promobar__eyebrow {
    display: inline-block;
    margin-bottom: var(--sf-sp-2);
    padding: 4px var(--sf-sp-3);
    border-radius: 999px;
    background: var(--sf-buy);
    color: #fff;
    font-size: 11px;
    font-weight: 800;
    letter-spacing: .08em;
    text-transform: uppercase;
}

/* Colour is set explicitly: `.sf :where(h1, h2, …)` is (0,1,0) and would
   otherwise force ink-coloured text onto an <h2> on the gradient. */
.sf-promobar__title {
    color: #fff;
    font-size: 21px;
    font-weight: 800;
    line-height: 1.25;
    letter-spacing: -.015em;
}

.sf-promobar__text {
    margin-top: var(--sf-sp-1);
    font-size: 14px;
    line-height: 1.6;
    color: rgba(255, 255, 255, .93);
}

.sf-promobar__actions {
    display: flex;
    flex-wrap: wrap;
    gap: var(--sf-sp-3);
}

.sf-promobar__actions .sf-btn {
    white-space: nowrap;
}

/* The default outline button is a white plate, which glares on the gradient —
   make the secondary action a translucent ghost instead. */
.sf-promobar .sf-btn--outline {
    background: rgba(255, 255, 255, .16);
    border-color: rgba(255, 255, 255, .5);
    color: #fff;
}

.sf-promobar .sf-btn--outline:hover:not(:disabled) {
    background: rgba(255, 255, 255, .28);
    border-color: #fff;
    color: #fff;
}

@media (max-width: 767px) {
    .sf-promobar__title {
        font-size: 18px;
    }

    /* Side-by-side full-width buttons are easier to tap than a wrapped row. */
    .sf-promobar__actions .sf-btn {
        flex: 1 1 auto;
    }
}

/* Hero-sized variant for archive and entry pages; the default promo strip stays compact. */


.sf-tiles {
    display: grid;
    gap: var(--sf-sp-4);
    grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
}

.sf-tile {
    display: flex;
    flex-direction: column;
    align-items: center;
    gap: var(--sf-sp-3);
    padding: var(--sf-sp-6) var(--sf-sp-4);
    background: var(--sf-surface);
    border-radius: var(--sf-radius-lg);
    box-shadow: var(--sf-shadow-xs);
    text-align: center;
    transition: box-shadow .22s var(--sf-ease), transform .22s var(--sf-ease);
}

.sf-tile:hover {
    box-shadow: var(--sf-shadow-sm);
    transform: translateY(-3px);
}

.sf-tile__icon {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    width: 60px;
    height: 60px;
    border-radius: 50%;
    background: var(--sf-brand-soft);
    color: var(--sf-brand);
}

/* Colourful icon chips — each category tile gets a different pastel accent. */
.sf-tile:nth-child(6n+1) .sf-tile__icon { background: #e6f5fd; color: #129fe5; }
.sf-tile:nth-child(6n+2) .sf-tile__icon { background: #fff1e6; color: #ff6a00; }
.sf-tile:nth-child(6n+3) .sf-tile__icon { background: #fdecec; color: #f9475b; }
.sf-tile:nth-child(6n+4) .sf-tile__icon { background: #e7f7f1; color: #0f9d8f; }
.sf-tile:nth-child(6n+5) .sf-tile__icon { background: #fdf3e0; color: #d97706; }
.sf-tile:nth-child(6n+6) .sf-tile__icon { background: #efe9fd; color: #7c3aed; }

.sf-tile__icon svg {
    width: 28px;
    height: 28px;
}

.sf-tile__name {
    font-size: 14px;
    font-weight: 600;
    color: var(--sf-ink);
    line-height: 1.45;
}

.sf-tile__count {
    font-size: 12px;
    color: var(--sf-muted);
}


/* -------------------------------------------------------------------------
   15. Page head, breadcrumb, filter bar
   ------------------------------------------------------------------------- */
.sf-page-head {
    margin-bottom: var(--sf-sp-6);
}

/* Single-column account layout (≤1023px): the page head sits directly above
   the content, so one step down from the desktop gap keeps the phone rhythm
   tight (matches the .sf-section mobile reduction). */
@media (max-width: 1023px) {
    .sf-page-head {
        margin-bottom: var(--sf-sp-5);
    }
}

.sf-page-title {
    font-size: 28px;
    font-weight: 800;
    letter-spacing: -.02em;
}

.sf-page-sub {
    margin-top: var(--sf-sp-2);
    font-size: 14px;
    color: var(--sf-muted);
}


/* -------------------------------------------------------------------------
   20. Pagination
   ------------------------------------------------------------------------- */
.sf-pagination {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: center;
    gap: var(--sf-sp-2);
    margin-top: var(--sf-sp-8);
}

.sf-page-link {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    min-width: 44px;
    height: 44px;
    padding-inline: var(--sf-sp-4);
    border-radius: 999px;
    background: transparent;
    font-size: 14px;
    font-weight: 600;
    color: var(--sf-ink-2);
    transition: background .18s var(--sf-ease), color .18s var(--sf-ease);
}

.sf-page-link:hover {
    background: var(--sf-fill);
    color: var(--sf-brand-dark);
}

.sf-page-link--active {
    background: var(--sf-brand);
    color: #fff;
}

.sf-page-link--disabled {
    opacity: .45;
    pointer-events: none;
}


/* ==========================================================================
   2. THE FRAME'S SEGMENTED CONTROL, renamed for this page
   (demo: .users__seg / .users__seg-btn, frame tokens mapped to shell tokens)
   ========================================================================== */
.ph-seg {
    display: inline-flex;
    gap: 1px;
    padding: 3px;
    border-radius: 999px;
    background: var(--sf-fill);
    box-shadow: inset 0 0 0 1px var(--shell-hair);
}

.ph-seg__btn {
    display: inline-flex;
    align-items: center;
    min-height: 32px;
    padding-inline: 12px;
    border: 0;
    border-radius: 999px;
    background: transparent;
    font-family: inherit;
    font-size: 14px;
    font-weight: 500;
    color: var(--sf-muted);
    cursor: pointer;
    transition: background .2s var(--sf-ease), color .2s var(--sf-ease), box-shadow .2s var(--sf-ease);
}

.ph-seg__btn:hover { color: var(--sf-ink); }

.ph-seg__btn.is-active {
    background: var(--sf-brand);
    color: #fff;
    box-shadow: 0 4px 12px -4px var(--shell-glow-1);
}


/* ==========================================================================
   3. PAGE-LOCAL ADDITIONS - the demo's own layer
   ========================================================================== */

/* ==========================================================================
   PAGE-LOCAL ADDITIONS - what this page needs beyond the frame

   The two sections above (tokens + frame) are copied verbatim from
   index.html via cars-database-demo.html, so keeping this block at
   the END means re-syncing the frame is a plain replacement.

   This page is a photo gallery, and a gallery needs one thing the shop does not
   have: photographs used as surfaces. `.ph-week` (the last seven daily picks) and
   `.ph-card` (a post cover) put pictures where the shop puts a product on a white
   square. The feature is seven cards INSIDE the content column (since 2026-09-24 -
   the full-bleed band is gone, see block 4), each photograph shown whole at its
   own ratio; everything else here is spacing and one segmented control.
   ========================================================================== */

/* 1. Spacing rhythm.
      .shell__view supplies the rhythm with `gap`; .sf-section carries
      margin-bottom: 32px and .sf-promobar 24px, so without this reset the two
      ADD (52px / 44px gaps) - the doubled-spacing problem recorded on
      /shop/account.

      The gap itself is also raised from the frame's 20px. One 20px step between
      EVERY block suits a news channel, whose blocks are thin (a headline list, a
      card row, a sidebar); this page's blocks are the page's whole subject - the
      feature photograph, ten tiles, three grid panels, the upload band - and at
      20px they read as one continuous section. 24px / 32px is the shop's own
      section rhythm (.sf-section is 32px, 48px >=768px) minus the part that came
      from the section's own margin-bottom, which the reset above hands back to
      the gap. ONE value for every block INSIDE a view, so the page head, the week
      strip, sections and the pager all sit on the same measurement - a single
      number to tune rather than per-block margins that can drift apart. */
/* The view's zero-specificity margin reset (`:where(.shell__view > *)`) is
    already in shell.css - the demo's own reset is redundant here. */

.shell .shell__view { gap: var(--sf-sp-6); }

@media (min-width: 768px) {
    .shell .shell__view { gap: var(--sf-sp-8); }
}

/* 2. No rail on this page (see the file header). The demo's frame reserved a
      300px second track for `.shell__rail` from 1280px; with the aside gone
      that track sat empty, so the demo forced its grid back to one column.
      The template's own `.shell__grid` is a single column already, so no
      override is needed - and the width matters here more than anywhere else
      in the family: seven feature photographs need the whole column to be
      worth looking at. */

/* 2b. NO BAND PAINTS A BACKGROUND (2026-09-22, user: "also remove the background
      color of the 2nd part"). The frame's `.shell__sheet` paints `--sf-surface`,
      which is white in light mode - where it matches the canvas and shows nothing -
      and #111 in dark mode, where it turned the page into blocks on a #000 canvas.
      The sheet is whole again since 2026-09-24 (the band that used to split it is
      gone), so this is the page's only band; what makes it read as one page is the
      container width and inset, not a shared fill.

      The declaration itself lives in the copied frame layer above, so this page
      neutralises it here instead of editing a block that is re-synced wholesale.
      The CARDS inside keep their surfaces (`.ph-card`, the tiles, the upload band):
      that edge is what makes them read as cards. Same treatment as
      photos-view-demo.html, so the two pages agree. */
/* (The demo's flat-sheet override is dropped: the template's `.shell__sheet`
   paints `--sf-content`, and `--plain` already sets that to #fff / #000.) */

/* 3. Page head - IN FLOW, ABOVE THE STRIP (2026-09-24, user: "Making the photo is
      too big. Maybe let's make the photo of the day as recent 7 days.").
      The h1 and its sub-line sat ON the photograph while the feature was one
      full-bleed band (they were `.ph-feature__head`, overlaid on the picture's top
      left with its own downward scrim, 2026-09-22); the band is gone, so they are
      the page's head again - a normal `.sf-page-head` above the week strip, in the
      theme's own colours, with no scrim behind them and nothing overlaying a
      photograph.

      The star stays: it is the editor's-pick marker, and it now rides on the head
      of a block of SEVEN rather than on the single picture it used to label.

      `.ph-page-title` only has to be a flex row so the 22px star and the 28px title
      share a baseline - the UA's inline vertical-align lands the star down by the
      descender. The `color: #fff` this rule used to carry went out with the scrim:
      on the page the title takes `--sf-ink` and flips with the theme like every
      other heading, which is also why the sub-line needs no page-local class any
      more - `.sf-page-sub` states its own muted colour. */
.sf-page-title.ph-page-title {
    display: flex;
    align-items: center;
    gap: var(--sf-sp-3);
}

.ph-page-star {
    flex: none;
    color: var(--sf-amber);
}

/* 4. Photo of the day - THE LAST SEVEN DAYS (2026-09-24, user: "Making the photo
      is too big. Maybe let's make the photo of the day as recent 7 days. But you
      need to remember that the photos can be aspect ratio of any kind.").

      WHAT REPLACED WHAT: the feature used to be ONE photograph in a full-bleed band
      as tall as the window (figure.ph-feature + stage + blurred backdrop +
      overlaid head/caption + like/save/share controls - all deleted 2026-09-24,
      see the tombstones below). Too big, and it showed a single day. It is now the
      seven picks, Sep 21 back to Sep 15, as seven cards inside the content column:
      the sheet is whole again (nothing is full bleed, so there is no band to split
      it around) and the h1 went back above the strip with `.sf-page-head`.

      ANY RATIO, NEVER CROPPED - the bargain the band struck, kept seven times:
        - THE SHAPE IS READ FROM THE IMAGE, not written by hand: the box IS the
          photograph (`height: auto` under a width:100% img, so the width/height
          attributes reserve it before the file loads - the same attributes every
          image needs anyway), and the ONE number the justified rows require -
          `--ar`, width / height - is computed by the page script from those same
          attributes (see the script). CSS is asked for it first and cannot do it:
          flex-grow accepts only a literal number, it cannot read a picture's
          intrinsic size. `var(--ar, 1)` falls back to equal-width columns if the
          script never runs. Drop in any src + width + height and every
          arrangement below handles it - nothing to tune.
        - >=1024px: TWO JUSTIFIED ROWS (3 + 4), the Flickr recipe. Each card's
          flex-grow is its ratio, so the row's width shares out IN PROPORTION TO
          THE SHAPES and every photograph in that row comes out THE SAME HEIGHT -
          equal widths would make a portrait stand taller than the panorama beside
          it. The split is explicit (two row wrappers, `flex-wrap: nowrap`) because
          seven does not divide and a flex line would recompose at every width.
        - <1024px: MULTICOL MASONRY - one column under 640px, two above. The row
          wrappers go `display: contents` so the cards fall straight into the
          columns, each at its natural height; column-major order, which the date
          on every card keeps readable.
        - WHY NOT A FIXED-BOX GRID: `aspect-ratio: 4/3; object-fit: cover` tidies
          any shape into a grid by CROPPING it - a 2:3 portrait loses 55% of the
          frame the photographer chose - and with seven mixed shapes it would also
          lie about what is inside. The post cards below still crop at 4:3: a grid
          of thumbnails is a different bargain from the editors' picks.

      SIZE: a justified row's height is free / SUM(ar) - the row's available width
      divided by the row's total ratio - so heights FOLLOW the shapes. The split is
      3 + 4 (2026-09-24, user: "make the top as 3 photos & bottom 4 photos"): row 1
      is three LANDSCAPES (16:9, 4:3, 3:2; SUM 4.61) and lands around 280px of
      photograph at this column's width; row 2 mixes the 8:5 landscape with three
      portraits (4:5, 3:4, 2:3; SUM 3.82) and lands around 330px - still portrait-
      heavy despite the landscape in it, because a row's height is its TOTAL ratio
      talking, not any one photo's. Neither row can be shrunk without cropping its
      frames or leaving the row unfilled; the shape decides the height, the code
      never does. The band this replaced was 100vh. The first card carries the
      `Today` chip (the plate `.ph-tag` already defines), so "photo of the day" is
      still findable inside the seven, and each card is ONE link to the post the
      band's photo pointed at. */
.ph-week {
    columns: 1;
    column-gap: var(--sf-sp-4);
    /* Extra air under the week strip, on top of .shell__view's gap (2026-10-20). */
    margin-bottom: var(--sf-sp-8);
}

/* The row wrappers are FLEX ROWS only >=1024px. Below that they must not be boxes
   at all: `display: contents` drops them from the layout so their cards become the
   multicol container's own content - which is also why the vertical rhythm there
   is each card's margin-bottom (a `gap` on a multicol container runs between the
   COLUMNS, not between the blocks inside one). */
.ph-week__row {
    display: contents;
}

/* The card IS the <a>: one destination (the post), one hover surface, no nested
   interactive elements - the same contract as `.ph-past__item`. `min-width: 0`
   matters >=1024: a flex item's automatic minimum is its min-content width, and one
   long unbroken word in a title would otherwise hold its card wider than its share
   of the row and break the justification. */
.ph-week__item {
    display: flex;
    flex-direction: column;
    gap: var(--sf-sp-3);
    break-inside: avoid; /* the photo and its captions never split across columns */
    margin-bottom: var(--sf-sp-4);
    min-width: 0;
    color: inherit;
}

@media (min-width: 640px) {
    .ph-week { columns: 2; }
}

@media (min-width: 1024px) {
    /* Multicol off (`columns: auto` clears both counters), the justified rows on.
       NOWRAP is the point: the line composition is the explicit 3 + 4 split in the
       markup, not whatever the wrap point happens to be at this width. */
    .ph-week { columns: auto; }

    .ph-week__row {
        display: flex;
        flex-wrap: nowrap;
        align-items: flex-start;
        gap: var(--sf-sp-4);
    }

    .ph-week__row + .ph-week__row { margin-top: var(--sf-sp-4); }

    /* `flex: var(--ar, 1) 1 0` - grow WITH the shape, basis 0, so the shares are
       purely proportional: width_i = free * ar_i / SUM(ar), and the box is ar_i
       tall (it follows the image), so height_i = free / SUM(ar) - the SAME number
       for every card in the row. That equal height is the whole trick; equal
       bases would not give it.
       `--ar` itself is COMPUTED at runtime: the page script reads each image's
       width and height attributes and writes the property, because flex-grow only
       accepts a literal - CSS cannot derive one from a picture. The `, 1` fallback
       is the no-script page: cards grow equally, i.e. tidy equal-width columns,
       and the box still takes the file's own ratio - degraded, not broken. */
    .ph-week__item {
        flex: var(--ar, 1) 1 0;
        margin-bottom: 0;
    }
}

/* The photograph's box IS the photograph: `height: auto` under a width:100% img
   makes the box follow the file's own intrinsic ratio, read from the width/height
   attributes the moment the HTML parses - so the height is reserved before the
   bytes arrive, with no ratio stated anywhere (the old `aspect-ratio: var(--ar)`
   went when the ratio became dynamic). Any shapes share the rows and columns
   with nothing to configure - the seven above run three photos over four
   (2026-09-24) - and a card
   whose attributes happen to disagree with the file reflows to the true shape
   instead of being cropped to a guess. Radius, hairline edge and fill mirror
   `.ph-past__media`, so this strip and the row below it read as one family of
   photographs. `position: relative` is what anchors the Today chip. */
.ph-week__media {
    position: relative;
    display: block;
    border-radius: var(--sf-radius);
    overflow: hidden;                   /* clips the hover zoom */
    background: var(--sf-fill);
    box-shadow: var(--sf-shadow-xs);
}

.ph-week__img {
    display: block;
    width: 100%;
    height: auto;                       /* the box takes the FILE's ratio, always */
    transition: transform .35s var(--sf-ease);
}

.ph-week__item:hover .ph-week__img { transform: scale(1.05); }

/* The caption - date over title - is `.ph-past`'s pair under new names, so the two
   photograph blocks can be retuned independently. Same clamp, and it matters more
   here: in a justified row a title that ran to three lines would push its own card's
   baseline out of line with its neighbours. */
.ph-week__date {
    display: block;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--sf-muted);
}

.ph-week__title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.45;
    color: var(--sf-ink);
}

.ph-week__item:hover .ph-week__title { color: var(--sf-brand); }

/* 4b. The layout-test switch (2026-09-24, user: "add a switch to randomize the
      portrait & landscape photo so that I could evaluate the results of various
      combinations").

      WHAT IT DOES NOT DO: move photos, dates or titles. The seven ratio SLOTS are
      re-dealt onto the seven cards - each card keeps its seed and its copy and
      receives a different width/height pair from the same pool, and picsum crops
      the SAME source file, so it is the same photograph in a new orientation.
      That keeps the thing under evaluation pure: the layout sees various
      combinations of landscape-over-portrait while chronology, the Today chip and
      every title stay where they were. OFF restores the designed arrangement (3
      photos on top, 4 below) because the original slots are captured once, before
      the first roll.

      The row lives OUTSIDE `.ph-week` on purpose: at <1024px that element is a
      multicol container, and a tools row inside it would become column content.
      As a sibling it is a normal view child and takes the page's standard gap.

      The track is a page-local toggle. The checkbox keeps its native semantics -
      the state is what a screen reader hears, and it stays in the tab order - with
      only the knob drawn; `--sf-line` is the off track (dark in dark mode, like the
      rest of the hairlines) and `--sf-brand` the on track, same pairing the
      segmented controls use. */
.ph-week-tools {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sf-sp-4);
}

.ph-switch {
    display: inline-flex;
    align-items: center;
    gap: var(--sf-sp-3);
    font-size: 13px;
    color: var(--sf-ink-2);
    cursor: pointer;
}

.ph-switch__input {
    position: absolute;                /* no offsets: it stays at its static spot */
    width: 1px;
    height: 1px;
    opacity: 0;
    pointer-events: none;              /* the LABEL is the click target instead */
}

.ph-switch__track {
    position: relative;
    flex: none;
    width: 40px;
    height: 22px;
    border-radius: 999px;
    background: var(--sf-line);
    transition: background .18s var(--sf-ease);
}

.ph-switch__track::after {
    content: "";
    position: absolute;
    inset-block-start: 2px;
    inset-inline-start: 2px;
    width: 18px;
    height: 18px;
    border-radius: 50%;
    background: #fff;
    box-shadow: 0 1px 2px rgba(0, 0, 0, .35);
    transition: transform .18s var(--sf-ease);
}

.ph-switch__input:checked + .ph-switch__track { background: var(--sf-brand); }
.ph-switch__input:checked + .ph-switch__track::after { transform: translateX(18px); }

.ph-switch__input:focus-visible + .ph-switch__track {
    outline: 2px solid var(--sf-brand);
    outline-offset: 2px;
}

/* DELETED WITH THE BAND, 2026-09-24: the stage, the anchor, the blurred backdrop
   and the contained photograph. The seven-day strip shows every file whole inside
   a box of its OWN shape instead (block 4), so there is nothing left to letterbox
   and nothing full-bleed left to fill. */

/* DELETED WITH THE BAND, 2026-09-24: the overlaid caption and its scrim
   (`.ph-feature__cap` / `__text`, plus their on-photo type). A seven-day strip has
   no caption to overlay - each card says its own date and title underneath it - so
   there is no out-of-flow box, no `--ph-stage` to anchor to and no scrim to keep
   white type legible over a photograph. The band's mechanics (fixed-length stage,
   `translate: 0 -100%`, why CSS cannot subtract an out-of-flow box's height) are
   kept in block 4's header as the reason they existed at all. */

/* 5. Popular categories - ten tiles, two rows of five once there is room.
      .sf-tiles' own rule is auto-fit minmax(140px, 1fr), which at this page's
      1312px column would open EIGHT tracks and leave the last two tiles alone on
      a second row. Ten items want a count that divides them: 5 x 2 above 768px,
      2 x 5 on a phone. The tiles themselves are untouched. */
.ph-cats { grid-template-columns: repeat(2, minmax(0, 1fr)); }

@media (min-width: 768px) {
    .ph-cats { grid-template-columns: repeat(5, minmax(0, 1fr)); }
}

/* 6. Previous features - six small covers, one row where there is room.
      Six divides evenly by 6, 3 and 2, so no row is ever left with an orphan. */
.ph-past {
    display: grid;
    gap: var(--sf-sp-4);
    grid-template-columns: repeat(2, minmax(0, 1fr));
}

@media (min-width: 768px) {
    .ph-past { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

@media (min-width: 1024px) {
    .ph-past { grid-template-columns: repeat(6, minmax(0, 1fr)); }
}

/* No card around these: the picture already separates itself with the radius and
   its own edge. Six white boxes in a row would be six more surfaces on a page
   that has plenty, and the date and title read fine under a photograph. */
.ph-past__item {
    display: flex;
    flex-direction: column;
    gap: var(--sf-sp-3);
    color: inherit;
}

.ph-past__media {
    display: block;
    border-radius: var(--sf-radius);
    overflow: hidden;                   /* clips the hover zoom */
    background: var(--sf-fill);
    box-shadow: var(--sf-shadow-xs);
}

.ph-past__img {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    transition: transform .35s var(--sf-ease);
}

.ph-past__item:hover .ph-past__img { transform: scale(1.05); }

.ph-past__date {
    display: block;
    font-size: 11px;
    font-weight: 700;
    letter-spacing: .08em;
    text-transform: uppercase;
    color: var(--sf-muted);
}

/* Two lines maximum. The titles are set by the community and one long one would
   push its own row's dates out of line with the other five. */
.ph-past__title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    font-size: 13px;
    font-weight: 600;
    line-height: 1.45;
    color: var(--sf-ink);
}

.ph-past__item:hover .ph-past__title { color: var(--sf-brand); }

/* 7. Post cards.
      The whole card is one link (a cover, a title and a byline are one
      destination), so the hover has to be carried by the parts: the cover zooms
      and the title takes the brand colour. */
.ph-card {
    display: flex;
    flex-direction: column;
    gap: var(--sf-sp-3);
    color: inherit;
}

.ph-card__media {
    position: relative;
    display: block;
    border-radius: var(--sf-radius);
    overflow: hidden;
    background: var(--sf-fill);
}

.ph-card__img {
    display: block;
    width: 100%;
    aspect-ratio: 4 / 3;
    object-fit: cover;
    transition: transform .35s var(--sf-ease);
}

.ph-card:hover .ph-card__img { transform: scale(1.04); }

.ph-card__title {
    display: -webkit-box;
    -webkit-box-orient: vertical;
    -webkit-line-clamp: 2;
    overflow: hidden;
    font-size: 15px;
    font-weight: 700;
    line-height: 1.35;
    color: var(--sf-ink);
    transition: color .18s var(--sf-ease);
}

.ph-card:hover .ph-card__title { color: var(--sf-brand); }

.ph-card__meta {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    gap: 6px;
    font-size: 12px;
    color: var(--sf-muted);
}

.ph-card__meta svg { flex: none; width: 13px; height: 13px; }

/* THE LIKE GLYPH IS AN OUTLINE, NOT A SOLID HEART (2026-09-22, user: "the favourite
   icon in black is not in a good faith, please update").

   What was wrong: the heart was the ONLY filled glyph in the row, and a solid
   `--sf-muted` (#4a5870) shape at 13px is, on screen, a black blob - while a FILLED
   heart is the icon for "you have liked this", so the count claimed a state the
   reader had not set. The rest of the page already says "outline": the camera on a
   cover, the save and the share buttons all carry `fill="none" stroke="currentColor"
   stroke-width="2"` in their markup.

   ONE RULE INSTEAD OF TWENTY-FOUR MARKUP EDITS. `fill="currentColor"` on the <svg> is
   a PRESENTATION ATTRIBUTE, and any CSS declaration beats one - so the whole grid
   follows this rule without a single card being touched. Scope is the meta row and
   nothing wider: the feature's own controls went out with the band (2026-09-24), so
   no other filled heart is left on the page to protect. Stroke width is in the
   24-unit viewBox: 2 renders as the
   same hairline as the camera's at 13px. */
.ph-card__meta svg path {
    fill: none;
    stroke: currentColor;
    stroke-width: 2;
    stroke-linecap: round;
    stroke-linejoin: round;
}

/* The two overlays on a cover - the photo count and the category.
      One shared rule, because they are the same object in two corners: a
      translucent dark plate that stays legible on any photograph, blurring
      whatever is behind it. Not .sf-badge: that is an opaque commerce badge
      (price, "New", "Sold out") and would punch a solid rectangle out of every
      picture on the page. */
.ph-count,
.ph-tag {
    position: absolute;
    display: inline-flex;
    align-items: center;
    gap: 5px;
    padding: 4px 9px;
    border-radius: 999px;
    background: rgba(8, 14, 24, .6);
    -webkit-backdrop-filter: blur(6px);
    backdrop-filter: blur(6px);
    font-size: 11px;
    font-weight: 700;
    line-height: 1.6;
    color: #fff;
}

.ph-count { inset-block-start: 10px; inset-inline-end: 10px; }
.ph-tag { inset-block-end: 10px; inset-inline-start: 10px; font-weight: 600; }
.ph-count svg { width: 12px; height: 12px; }

/* 8. The Latest / Popular / Following control.
      .ph-seg is the frame's segmented control (the shop uses it for car
      types). It is inline-flex and must keep hugging its own text, so it is
      pinned as a flex item that cannot shrink in the row below. */
.ph-tabs { flex: none; }

/* The control row, and the one line that explains the whole grid under it: a
   post holds ONE photo or up to ten, and the badge on each cover is how many.
   The rule is repeated in the upload band, but it belongs HERE too - it is what
   makes a "1" badge and a "10" badge read as the same kind of thing. */
.ph-tabbar {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    justify-content: space-between;
    gap: var(--sf-sp-4);
    margin-bottom: var(--sf-sp-5);
}

.ph-note {
    font-size: 13px;
    line-height: 1.5;
    color: var(--sf-muted);
}

/* `hidden` needs an explicit rule. The UA's [hidden] { display: none } is the
   lowest-priority origin and LOSES to any author `display: grid` on the same
   element (0,1,0 vs 0,0,0), so without this line the three panels would all be
   visible at once - the sibling pages hit the same trap with .lb-panel. */
.ph-panel[hidden] { display: none; }

/* 9. Upload band - the three steps under it.
      The band is .sf-promobar (frame component) and states the RULE: one photo or
      up to ten in a post, each with its own caption. The steps say what that means
      in three short lines, so the promise on the band is not the only thing the
      page says about uploading. */
.ph-steps {
    display: grid;
    gap: var(--sf-sp-4);
}

@media (min-width: 768px) {
    .ph-steps { grid-template-columns: repeat(3, minmax(0, 1fr)); }
}

.ph-step {
    display: flex;
    align-items: flex-start;
    gap: var(--sf-sp-4);
    padding: var(--sf-sp-5);
    background: var(--sf-surface);
    border-radius: var(--sf-radius-lg);
    box-shadow: var(--sf-shadow-xs);
}

/* The numeral is brand blue as TEXT on a brand tint - which is the one
   combination that needs --shell-link rather than --sf-brand: solid brand on this
   tint measures 3.15:1, and #8fd0f5 in dark mode is the readable step. */
.ph-step__num {
    display: inline-flex;
    align-items: center;
    justify-content: center;
    flex: none;
    width: 32px;
    height: 32px;
    border-radius: 999px;
    background: var(--sf-brand-soft);
    color: var(--shell-link);
    font-size: 14px;
    font-weight: 800;
}

.ph-step__title {
    font-size: 14px;
    font-weight: 700;
}

.ph-step__text {
    margin-top: 4px;
    font-size: 13px;
    line-height: 1.55;
    color: var(--sf-muted);
}

/* 10. Footer - the demo's five-group foot grid (its frame stopped at four
       tracks, so its fifth group sat alone on a second row and it forced
       3-up / 5-up). Dropped with the frame: the template's own
       partials/shell-foot.php owns the footer. */

/* 11. The count under the pager. It belongs to the post list, and it exists so
       "600" in the pager has something to argue with. */
.ph-showing {
    text-align: center;
    font-size: 13px;
    color: var(--sf-muted);
}

/* 12. Flat canvas - no grey.
      The frame paints a grey page behind a white sheet (the content-site
      convention, "one number to tune" in the token block above). On this page
      that grey only ever shows as strips down the outside edges, so the canvas
      is the surface colour and the page reads as one flat sheet.

      TWO declarations, because the frame paints the grey twice:
      - `.users` re-points `--sf-page`, which body reads. Re-pointing the TOKEN
        rather than declaring a background on .sf-main keeps anything else that
        reads it in step.
      - `html` carries its own background in the frame (it is the strip iOS
        Safari paints behind the notch / status bar, and the strip the
        `scrollbar-gutter` reservation leaves down the right edge). The root
        element having a background is also what stops the body's background
        propagating to the canvas, so leaving it grey would keep a grey edge on
        an otherwise white page.

      Light mode only: dark mode keeps #000, because there the canvas is what a
      card's #111 surface steps UP from (storefront.css 27 - on a dark page the
      shadows are invisible, so tone is the only thing that defines a card edge).
      Flattening that to #111 would delete the edge. Written as a literal pair
      rather than `light-dark(var(--sf-surface), ...)`: nesting light-dark() is
      not something to bet a page's background on. */
html:root { background-color: light-dark(#ffffff, #000000); }

/* ==========================================================================
   4. SPECIFICITY BUMPS - this sheet loads BEFORE shell.css
   ========================================================================== */
/* (a) The h2 on the gradient. shell.css's `.shell :where(h1,h2,h3,h4)` is
   (0,1,0), the same as a bare `.sf-promobar__title`, and shell.css loads later
   - so the demo's white-on-gradient title would come out ink-coloured. */
.sf-promobar .sf-promobar__title { color: #fff; }

/* (b) The pager's own top margin. shell.css's view reset (`:where(.shell__view
    > *)`) zeroes margin-BOTTOM only, and .sf-pagination carries margin-top: 32px. */
.shell__view > .sf-pagination { margin-top: 0; }
