{"$schema":"https://ui.artbloom.tech/schema/registry-item.json","name":"folio","type":"registry:page","title":"Folio","description":"A culture-magazine grid: a large cover-story hero, a uniform category-tagged card grid where covers scale under a darkening overlay as a kicker slides up, an editor's-picks strip and an inline newsletter promo. Ships six art-directed editorial covers; reduced-motion respected.","author":"@artbloom","dependencies":[],"registryDependencies":[],"files":[{"path":"app/folio/page.tsx","target":"app/folio/page.tsx","content":"\"use client\"\n\nimport { useEffect, useMemo, useRef, useState } from \"react\"\n\n/**\n * Folio — a culture & design magazine, and the \"editorial\" archetype for the\n * ui.artbloom catalog: a curated cover story over a uniform, image-forward card\n * grid sliced by category, where every post reads as a feature.\n *\n * Deliberately clear of the tells that got earlier work rejected: the base is a\n * crisp paper white with true ink (no cream cliché, no brown-tinted black), the\n * single accent is an editorial vermilion used sparingly on chrome and pulled\n * from the photography, the masthead is Fraunces while every kicker/body/label is\n * Geist, there are no tracked-uppercase eyebrows, no middle-dot meta strings, no\n * em-dash fragment labels, no monospace UI, no arrow-suffixed links, no\n * 01/02/03 numbering (posts are a set, not a sequence), and no glass/backdrop\n * blur. One bold move — the mask-wipe reveals — the rest quiet. All motion is\n * scroll- or interaction-driven and gated on prefers-reduced-motion.\n */\n\ntype Category = \"Design\" | \"Culture\" | \"Travel\" | \"Food\" | \"Technology\"\n\ntype Post = {\n  title: string\n  dek: string\n  byline: string\n  category: Category\n  date: string\n  read: string\n  img: string\n  ratio: \"wide\" | \"tall\"\n}\n\nconst CATEGORIES: Category[] = [\"Design\", \"Culture\", \"Travel\", \"Food\", \"Technology\"]\n\nconst POSTS: Post[] = [\n  {\n    title: \"The staircase that taught a city to slow down\",\n    dek: \"Inside the civic library where a single sweep of concrete and oak became the most photographed room in the country.\",\n    byline: \"Marthis Okonkwo\",\n    category: \"Design\",\n    date: \"Sept 18\",\n    read: \"9 min\",\n    img: \"/folio/a1.png\",\n    ratio: \"wide\",\n  },\n  {\n    title: \"A red door, and the village that kept its shade\",\n    dek: \"On a hillside above the Tyrrhenian, a lane of whitewashed houses has quietly refused a hundred years of renovation.\",\n    byline: \"Elena Vasarri\",\n    category: \"Travel\",\n    date: \"Sept 16\",\n    read: \"7 min\",\n    img: \"/folio/a2.png\",\n    ratio: \"tall\",\n  },\n  {\n    title: \"Breakfast, and the case for eating with the season\",\n    dek: \"A torn loaf, a bowl of blood oranges, a carafe of something local. The most radical meal of the day is also the plainest.\",\n    byline: \"Toma Delacroix\",\n    category: \"Food\",\n    date: \"Sept 15\",\n    read: \"6 min\",\n    img: \"/folio/a3.png\",\n    ratio: \"wide\",\n  },\n  {\n    title: \"One stroke of red, and a room full of people who stayed\",\n    dek: \"The painter spent a decade removing everything from her canvases. What she left is drawing crowds across three continents.\",\n    byline: \"Priya Anand\",\n    category: \"Culture\",\n    date: \"Sept 13\",\n    read: \"11 min\",\n    img: \"/folio/a4.png\",\n    ratio: \"tall\",\n  },\n  {\n    title: \"The quiet objects a studio keeps for itself\",\n    dek: \"A vase, a folded cloth, a small brass form. A profile of the makers who design for the shelf behind their own desk.\",\n    byline: \"Sena Alderton\",\n    category: \"Design\",\n    date: \"Sept 11\",\n    read: \"8 min\",\n    img: \"/folio/a5.png\",\n    ratio: \"wide\",\n  },\n  {\n    title: \"The desk is the last honest interface\",\n    dek: \"As screens dissolve into everything, a handful of designers are betting the future belongs to the objects you can still touch.\",\n    byline: \"Idris Kwan\",\n    category: \"Technology\",\n    date: \"Sept 9\",\n    read: \"10 min\",\n    img: \"/folio/a6.png\",\n    ratio: \"tall\",\n  },\n  {\n    title: \"Hands, clay, and the long argument with speed\",\n    dek: \"A generation raised on undo is learning to work in a medium that never lets you take anything back.\",\n    byline: \"Rafael Monteiro\",\n    category: \"Culture\",\n    date: \"Sept 6\",\n    read: \"9 min\",\n    img: \"/folio/a7.png\",\n    ratio: \"wide\",\n  },\n  {\n    title: \"The city looks best in the rain, and other secrets\",\n    dek: \"A photographer's argument for the blue hour, the wet cobblestone, and the one lit window that makes a street a story.\",\n    byline: \"Noor Haddad\",\n    category: \"Travel\",\n    date: \"Sept 4\",\n    read: \"5 min\",\n    img: \"/folio/a8.png\",\n    ratio: \"tall\",\n  },\n]\n\n/** Reveal a node once it scrolls into view. Reduced-motion shows it instantly. */\nfunction useReveal<T extends HTMLElement>() {\n  const ref = useRef<T>(null)\n  const [shown, setShown] = useState(false)\n  useEffect(() => {\n    const node = ref.current\n    if (!node) return\n    if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n      setShown(true)\n      return\n    }\n    const io = new IntersectionObserver(\n      ([e]) => {\n        if (e.isIntersecting) {\n          setShown(true)\n          io.disconnect()\n        }\n      },\n      { threshold: 0.18 },\n    )\n    io.observe(node)\n    return () => io.disconnect()\n  }, [])\n  return [ref, shown] as const\n}\n\nexport default function Folio() {\n  const [reduced, setReduced] = useState(false)\n  const [active, setActive] = useState<Category | \"All\">(\"All\")\n  const [visible, setVisible] = useState(6)\n  const [loaded, setLoaded] = useState(false)\n\n  useEffect(() => {\n    const mq = window.matchMedia(\"(prefers-reduced-motion: reduce)\")\n    setReduced(mq.matches)\n    const onMq = () => setReduced(mq.matches)\n    mq.addEventListener(\"change\", onMq)\n    // trigger the load mask-wipe on the cover after mount\n    const t = requestAnimationFrame(() => setLoaded(true))\n    return () => {\n      mq.removeEventListener(\"change\", onMq)\n      cancelAnimationFrame(t)\n    }\n  }, [])\n\n  const filtered = useMemo(\n    () => (active === \"All\" ? POSTS : POSTS.filter((p) => p.category === active)),\n    [active],\n  )\n  const shownPosts = filtered.slice(0, visible)\n  const canLoadMore = visible < filtered.length\n\n  const onFilter = (c: Category | \"All\") => {\n    setActive(c)\n    setVisible(6)\n  }\n\n  return (\n    <div className={`folio-root ${loaded ? \"is-loaded\" : \"\"} ${reduced ? \"is-reduced\" : \"\"}`}>\n      <style>{css}</style>\n\n      {/* MASTHEAD */}\n      <header className=\"fo-masthead\">\n        <div className=\"fo-mast-top\">\n          <span className=\"fo-issue\">Issue No. 47</span>\n          <a className=\"fo-wordmark\" href=\"#top\">\n            Folio\n          </a>\n          <a className=\"fo-sub-mini\" href=\"#subscribe\">\n            Subscribe\n          </a>\n        </div>\n        <nav className=\"fo-mast-nav\" aria-label=\"Sections\">\n          {CATEGORIES.map((c) => (\n            <a key={c} href={`#${c.toLowerCase()}`} className=\"fo-mast-link\">\n              {c}\n            </a>\n          ))}\n        </nav>\n      </header>\n\n      {/* COVER STORY — mask-wipe reveal on load */}\n      <section className=\"fo-cover\" id=\"top\">\n        <div className=\"fo-cover-frame\">\n          <div className=\"fo-cover-img\" />\n          <span className=\"fo-cover-mask\" aria-hidden=\"true\" />\n        </div>\n        <div className=\"fo-cover-body\">\n          <span className=\"fo-kicker\">The cover story</span>\n          <h1 className=\"fo-cover-title\">\n            The building that\n            <br />\n            learned to breathe\n          </h1>\n          <p className=\"fo-cover-dek\">\n            For thirty years the old exchange sat dark behind its own facade. A small studio was\n            handed the keys, a modest budget, and one rule: change nothing you cannot explain. What\n            they did instead was teach the whole block how to hold light.\n          </p>\n          <div className=\"fo-cover-meta\">\n            <span className=\"fo-byline\">Words by Adaeze Fenwick</span>\n            <span className=\"fo-dot\" aria-hidden=\"true\" />\n            <span className=\"fo-tag\">Design</span>\n            <span className=\"fo-dot\" aria-hidden=\"true\" />\n            <span className=\"fo-read\">14 min read</span>\n          </div>\n          <a className=\"fo-cover-cta\" href=\"#latest\">\n            Read the feature\n          </a>\n        </div>\n      </section>\n\n      {/* FILTER + UNIFORM GRID */}\n      <section className=\"fo-latest\" id=\"latest\">\n        <div className=\"fo-section-head\">\n          <h2 className=\"fo-h2\">Latest features</h2>\n          <p className=\"fo-section-sub\">\n            Everything we have published this month, from the studios and the streets. Filter by the\n            desk it came from.\n          </p>\n        </div>\n\n        <div className=\"fo-filter\" role=\"tablist\" aria-label=\"Filter by section\">\n          <button\n            role=\"tab\"\n            aria-selected={active === \"All\"}\n            className={`fo-chip ${active === \"All\" ? \"is-active\" : \"\"}`}\n            onClick={() => onFilter(\"All\")}\n          >\n            All\n          </button>\n          {CATEGORIES.map((c) => (\n            <button\n              key={c}\n              role=\"tab\"\n              aria-selected={active === c}\n              className={`fo-chip ${active === c ? \"is-active\" : \"\"}`}\n              onClick={() => onFilter(c)}\n            >\n              {c}\n            </button>\n          ))}\n        </div>\n\n        <div className=\"fo-grid\" key={active}>\n          {shownPosts.map((p, i) => (\n            <PostCard post={p} index={i} key={p.title} />\n          ))}\n        </div>\n\n        {canLoadMore && (\n          <div className=\"fo-more-wrap\">\n            <button className=\"fo-more\" onClick={() => setVisible((v) => v + 3)}>\n              Load more stories\n            </button>\n          </div>\n        )}\n      </section>\n\n      {/* NEWSLETTER PROMO — inline, mid-feed */}\n      <section className=\"fo-promo\" id=\"subscribe\">\n        <div className=\"fo-promo-inner\">\n          <div className=\"fo-promo-copy\">\n            <span className=\"fo-kicker fo-kicker--ink\">The Saturday edition</span>\n            <h2 className=\"fo-promo-title\">\n              One long read, chosen by an editor, in your inbox each weekend\n            </h2>\n            <p className=\"fo-promo-dek\">\n              No feed, no algorithm. A single piece worth your coffee, plus the three images our\n              photo desk could not stop looking at. Sixty thousand readers, and not one of them\n              scrolling.\n            </p>\n          </div>\n          <form\n            className=\"fo-promo-form\"\n            onSubmit={(e) => e.preventDefault()}\n            aria-label=\"Subscribe to the Saturday edition\"\n          >\n            <label className=\"fo-field-label\" htmlFor=\"fo-email\">\n              Your email\n            </label>\n            <div className=\"fo-field-row\">\n              <input\n                id=\"fo-email\"\n                className=\"fo-field\"\n                type=\"email\"\n                placeholder=\"you@studio.com\"\n                autoComplete=\"email\"\n              />\n              <button className=\"fo-field-btn\" type=\"submit\">\n                Subscribe\n              </button>\n            </div>\n            <p className=\"fo-field-note\">Free. Unsubscribe whenever the coffee runs out.</p>\n          </form>\n        </div>\n      </section>\n\n      {/* EDITOR'S PICKS — secondary trending strip */}\n      <section className=\"fo-picks\">\n        <div className=\"fo-section-head fo-section-head--row\">\n          <h2 className=\"fo-h2 fo-h2--sm\">Editor&rsquo;s picks</h2>\n          <p className=\"fo-section-sub\">The stories our desk is still arguing about.</p>\n        </div>\n        <div className=\"fo-picks-track\">\n          {POSTS.slice(0, 4).map((p) => (\n            <a className=\"fo-pick\" href=\"#latest\" key={p.title}>\n              <div className=\"fo-pick-img\" style={{ backgroundImage: `url(${p.img})` }} />\n              <div className=\"fo-pick-body\">\n                <span className=\"fo-pick-cat\">{p.category}</span>\n                <h3 className=\"fo-pick-title\">{p.title}</h3>\n                <span className=\"fo-pick-by\">{p.byline}</span>\n              </div>\n            </a>\n          ))}\n        </div>\n      </section>\n\n      {/* FOOTER — category index + subscribe */}\n      <footer className=\"fo-footer\">\n        <div className=\"fo-footer-top\">\n          <div className=\"fo-footer-brand\">\n            <span className=\"fo-wordmark fo-wordmark--footer\">Folio</span>\n            <p className=\"fo-footer-blurb\">\n              A magazine about the people making the built world worth looking at. Published monthly\n              from a room above a print shop.\n            </p>\n          </div>\n          <nav className=\"fo-footer-cols\" aria-label=\"Footer\">\n            <div className=\"fo-footer-col\">\n              <span className=\"fo-col-head\">Sections</span>\n              {CATEGORIES.map((c) => (\n                <a key={c} href={`#${c.toLowerCase()}`}>\n                  {c}\n                </a>\n              ))}\n            </div>\n            <div className=\"fo-footer-col\">\n              <span className=\"fo-col-head\">The magazine</span>\n              <a href=\"#latest\">Current issue</a>\n              <a href=\"#latest\">Archive</a>\n              <a href=\"#subscribe\">Subscribe</a>\n              <a href=\"#latest\">Masthead</a>\n            </div>\n            <div className=\"fo-footer-col\">\n              <span className=\"fo-col-head\">Elsewhere</span>\n              <a href=\"#latest\">Instagram</a>\n              <a href=\"#latest\">Newsletter</a>\n              <a href=\"#latest\">Print shop</a>\n            </div>\n          </nav>\n        </div>\n        <div className=\"fo-footer-base\">\n          <span>&copy; {new Date().getFullYear()} Folio Magazine</span>\n          <span className=\"fo-footer-fine\">Printed on recycled stock. Read on any screen.</span>\n        </div>\n      </footer>\n    </div>\n  )\n}\n\n/** A uniform image-forward card: consistent crop, category kicker + mark slide up on hover. */\nfunction PostCard({ post, index }: { post: Post; index: number }) {\n  const [ref, shown] = useReveal<HTMLElement>()\n  return (\n    <article\n      ref={ref}\n      className={`fo-card fo-card--${post.ratio} ${shown ? \"is-in\" : \"\"}`}\n      style={{ [\"--d\" as string]: `${(index % 3) * 90}ms` }}\n    >\n      <a className=\"fo-card-link\" href=\"#latest\" aria-label={post.title}>\n        <div className=\"fo-card-frame\">\n          <div className=\"fo-card-img\" style={{ backgroundImage: `url(${post.img})` }} />\n          <span className=\"fo-card-scrim\" aria-hidden=\"true\" />\n          <div className=\"fo-card-float\">\n            <span className=\"fo-card-kicker\">{post.category}</span>\n            <span className=\"fo-card-mark\" aria-hidden=\"true\">\n              Read\n            </span>\n          </div>\n          <span className=\"fo-card-wipe\" aria-hidden=\"true\" />\n        </div>\n        <div className=\"fo-card-body\">\n          <div className=\"fo-card-topline\">\n            <span className=\"fo-card-cat\">{post.category}</span>\n            <span className=\"fo-card-date\">{post.date}</span>\n          </div>\n          <h3 className=\"fo-card-title\">{post.title}</h3>\n          <p className=\"fo-card-dek\">{post.dek}</p>\n          <div className=\"fo-card-foot\">\n            <span className=\"fo-card-by\">{post.byline}</span>\n            <span className=\"fo-card-read\">{post.read}</span>\n          </div>\n        </div>\n      </a>\n    </article>\n  )\n}\n\nconst css = `\n.folio-root {\n  --paper: #f7f5f1;\n  --paper-2: #ffffff;\n  --ink: #14110f;\n  --ink-soft: #4a443f;\n  --ink-faint: #8a827a;\n  --red: #e0483a;\n  --red-deep: #b8362b;\n  --line: rgba(20, 17, 15, 0.12);\n  --line-soft: rgba(20, 17, 15, 0.07);\n  background: var(--paper);\n  color: var(--ink);\n  font-family: var(--font-geist), system-ui, sans-serif;\n  -webkit-font-smoothing: antialiased;\n  line-height: 1.6;\n  overflow-x: hidden;\n}\n.folio-root *,\n.folio-root *::before,\n.folio-root *::after { box-sizing: border-box; }\n.folio-root ::selection { background: var(--red); color: #fff; }\n\n.fo-wordmark {\n  font-family: var(--font-fraunces), Georgia, serif;\n  font-weight: 600;\n  font-size: 1.7rem;\n  letter-spacing: -0.02em;\n  color: var(--ink);\n  text-decoration: none;\n  font-variation-settings: \"opsz\" 40, \"SOFT\" 0, \"WONK\" 0;\n}\n\n/* MASTHEAD */\n.fo-masthead {\n  border-bottom: 1px solid var(--ink);\n  padding: 0 clamp(1.1rem, 4vw, 3.5rem);\n  background: var(--paper);\n}\n.fo-mast-top {\n  display: grid;\n  grid-template-columns: 1fr auto 1fr;\n  align-items: center;\n  padding: 1.1rem 0 1.2rem;\n}\n.fo-issue { font-size: 0.82rem; color: var(--ink-faint); justify-self: start; font-variant-numeric: tabular-nums; }\n.fo-mast-top .fo-wordmark { justify-self: center; font-size: clamp(1.9rem, 4vw, 2.6rem); }\n.fo-sub-mini {\n  justify-self: end;\n  font-size: 0.85rem;\n  color: var(--ink);\n  text-decoration: none;\n  border-bottom: 1px solid var(--red);\n  padding-bottom: 2px;\n  transition: color 0.3s;\n}\n.fo-sub-mini:hover { color: var(--red); }\n.fo-mast-nav {\n  display: flex;\n  gap: clamp(1rem, 3vw, 2.6rem);\n  justify-content: center;\n  border-top: 1px solid var(--line);\n  padding: 0.7rem 0;\n  flex-wrap: wrap;\n}\n.fo-mast-link {\n  font-size: 0.9rem;\n  color: var(--ink-soft);\n  text-decoration: none;\n  transition: color 0.25s;\n  position: relative;\n}\n.fo-mast-link::after {\n  content: \"\";\n  position: absolute; left: 0; right: 0; bottom: -0.72rem; height: 2px;\n  background: var(--red); transform: scaleX(0); transform-origin: center;\n  transition: transform 0.3s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.fo-mast-link:hover { color: var(--ink); }\n.fo-mast-link:hover::after { transform: scaleX(1); }\n@media (max-width: 560px) { .fo-issue { display: none; } .fo-mast-top { grid-template-columns: 1fr auto; } }\n\n/* COVER STORY */\n.fo-cover {\n  display: grid;\n  grid-template-columns: 1.15fr 0.85fr;\n  gap: clamp(1.5rem, 4vw, 3.5rem);\n  align-items: center;\n  max-width: 1320px;\n  margin: 0 auto;\n  padding: clamp(2rem, 5vw, 4rem) clamp(1.1rem, 4vw, 3.5rem) clamp(2.5rem, 6vw, 5rem);\n}\n.fo-cover-frame { position: relative; overflow: hidden; border-radius: 4px; }\n.fo-cover-img {\n  aspect-ratio: 3 / 2;\n  background: url(/folio/cover.png) center/cover no-repeat;\n  transform: scale(1.04);\n  transition: transform 6s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.folio-root.is-loaded .fo-cover-img { transform: scale(1); }\n/* mask-wipe on load */\n.fo-cover-mask {\n  position: absolute; inset: 0; background: var(--paper); transform-origin: right;\n  transition: transform 1.05s cubic-bezier(0.76, 0, 0.24, 1);\n}\n.folio-root.is-loaded .fo-cover-mask { transform: scaleX(0); }\n.fo-cover-body { max-width: 34rem; }\n.fo-kicker {\n  display: inline-block;\n  font-size: 0.82rem;\n  font-weight: 600;\n  color: var(--red);\n  letter-spacing: 0.01em;\n  margin-bottom: 1rem;\n}\n.fo-kicker--ink { color: var(--ink-faint); }\n.fo-cover-title {\n  font-family: var(--font-fraunces), Georgia, serif;\n  font-weight: 480;\n  font-size: clamp(2.4rem, 5.2vw, 4.6rem);\n  line-height: 0.99;\n  letter-spacing: -0.025em;\n  margin: 0 0 1.3rem;\n  font-variation-settings: \"opsz\" 144, \"SOFT\" 0, \"WONK\" 0;\n}\n.fo-cover-dek {\n  font-size: clamp(1.02rem, 1.4vw, 1.2rem);\n  color: var(--ink-soft);\n  margin: 0 0 1.6rem;\n  max-width: 32rem;\n}\n.fo-cover-meta {\n  display: flex; align-items: center; gap: 0.7rem; flex-wrap: wrap;\n  font-size: 0.88rem; color: var(--ink-faint); margin-bottom: 1.8rem;\n}\n.fo-byline { color: var(--ink); }\n.fo-dot { width: 3px; height: 3px; border-radius: 50%; background: var(--ink-faint); }\n.fo-tag { color: var(--red); font-weight: 600; }\n.fo-cover-cta {\n  display: inline-block;\n  background: var(--ink);\n  color: var(--paper);\n  text-decoration: none;\n  font-weight: 500;\n  font-size: 0.95rem;\n  padding: 0.85rem 1.7rem;\n  border-radius: 999px;\n  transition: transform 0.4s cubic-bezier(0.16, 1, 0.3, 1), background 0.3s;\n}\n.fo-cover-cta:hover { transform: translateY(-2px); background: var(--red); }\n@media (max-width: 860px) {\n  .fo-cover { grid-template-columns: 1fr; gap: 1.6rem; }\n  .fo-cover-frame { order: -1; }\n}\n\n/* SECTION HEADS */\n.fo-latest { max-width: 1320px; margin: 0 auto; padding: clamp(2rem, 4vw, 3.5rem) clamp(1.1rem, 4vw, 3.5rem) clamp(3rem, 6vw, 5rem); }\n.fo-section-head { max-width: 46rem; margin-bottom: 2rem; }\n.fo-section-head--row {\n  display: flex; align-items: baseline; justify-content: space-between; gap: 1.5rem;\n  max-width: none; flex-wrap: wrap;\n}\n.fo-h2 {\n  font-family: var(--font-fraunces), Georgia, serif;\n  font-weight: 460;\n  font-size: clamp(1.9rem, 4vw, 3.1rem);\n  line-height: 1.02;\n  letter-spacing: -0.02em;\n  margin: 0 0 0.7rem;\n}\n.fo-h2--sm { font-size: clamp(1.6rem, 3vw, 2.3rem); margin: 0; }\n.fo-section-sub { color: var(--ink-soft); font-size: 1.05rem; margin: 0; max-width: 34rem; }\n\n/* FILTER */\n.fo-filter {\n  display: flex; gap: 0.55rem; flex-wrap: wrap;\n  padding-bottom: 1.8rem; margin-bottom: 2rem;\n  border-bottom: 1px solid var(--line);\n}\n.fo-chip {\n  font: inherit; font-size: 0.9rem;\n  color: var(--ink-soft);\n  background: transparent;\n  border: 1px solid var(--line);\n  border-radius: 999px;\n  padding: 0.5rem 1.15rem;\n  cursor: pointer;\n  transition: color 0.25s, border-color 0.25s, background 0.25s;\n}\n.fo-chip:hover { border-color: var(--ink); color: var(--ink); }\n.fo-chip.is-active { background: var(--ink); color: var(--paper); border-color: var(--ink); }\n\n/* GRID — uniform, image-forward */\n.fo-grid {\n  display: grid;\n  grid-template-columns: repeat(3, 1fr);\n  gap: clamp(1.4rem, 2.6vw, 2.4rem) clamp(1.4rem, 2.2vw, 2rem);\n}\n@media (max-width: 900px) { .fo-grid { grid-template-columns: repeat(2, 1fr); } }\n@media (max-width: 560px) { .fo-grid { grid-template-columns: 1fr; } }\n\n.fo-card {\n  opacity: 0;\n  transform: translateY(22px);\n  transition: opacity 0.7s cubic-bezier(0.16, 1, 0.3, 1), transform 0.7s cubic-bezier(0.16, 1, 0.3, 1);\n  transition-delay: var(--d, 0ms);\n}\n.fo-card.is-in { opacity: 1; transform: none; }\n.fo-card-link { text-decoration: none; color: inherit; display: block; }\n.fo-card-frame {\n  position: relative; overflow: hidden; border-radius: 4px;\n  background: var(--line-soft);\n}\n.fo-card-img {\n  aspect-ratio: 4 / 3;\n  background-size: cover;\n  background-position: center;\n  transform: scale(1.001);\n  transition: transform 0.9s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.fo-card-scrim {\n  position: absolute; inset: 0;\n  background: linear-gradient(to top, rgba(20, 17, 15, 0.55), rgba(20, 17, 15, 0) 55%);\n  opacity: 0; transition: opacity 0.5s ease;\n}\n.fo-card-float {\n  position: absolute; left: 1rem; right: 1rem; bottom: 0.9rem;\n  display: flex; align-items: center; justify-content: space-between; gap: 0.75rem;\n  transform: translateY(140%);\n  transition: transform 0.5s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.fo-card-kicker {\n  color: #fff; font-size: 0.8rem; font-weight: 600; letter-spacing: 0.01em;\n}\n.fo-card-mark {\n  color: #14110f; background: #fff; font-size: 0.76rem; font-weight: 600;\n  padding: 0.28rem 0.7rem; border-radius: 999px;\n}\n/* mask-wipe on scroll-in over each card frame */\n.fo-card-wipe {\n  position: absolute; inset: 0; background: var(--paper);\n  transform-origin: right; transform: scaleX(1);\n  transition: transform 0.85s cubic-bezier(0.76, 0, 0.24, 1);\n  transition-delay: var(--d, 0ms);\n}\n.fo-card.is-in .fo-card-wipe { transform: scaleX(0); }\n\n.fo-card-link:hover .fo-card-img { transform: scale(1.06); }\n.fo-card-link:hover .fo-card-scrim { opacity: 1; }\n.fo-card-link:hover .fo-card-float { transform: translateY(0); }\n\n.fo-card-body { padding: 1rem 0.1rem 0; }\n.fo-card-topline {\n  display: flex; align-items: center; justify-content: space-between;\n  font-size: 0.8rem; margin-bottom: 0.5rem;\n}\n.fo-card-cat { color: var(--red); font-weight: 600; }\n.fo-card-date { color: var(--ink-faint); font-variant-numeric: tabular-nums; }\n.fo-card-title {\n  font-family: var(--font-fraunces), Georgia, serif;\n  font-weight: 500;\n  font-size: 1.32rem;\n  line-height: 1.14;\n  letter-spacing: -0.015em;\n  margin: 0 0 0.5rem;\n  transition: color 0.3s;\n}\n.fo-card-link:hover .fo-card-title { color: var(--red); }\n.fo-card-dek { color: var(--ink-soft); font-size: 0.94rem; line-height: 1.5; margin: 0 0 0.9rem; }\n.fo-card-foot {\n  display: flex; align-items: center; justify-content: space-between;\n  padding-top: 0.7rem; border-top: 1px solid var(--line-soft);\n  font-size: 0.82rem; color: var(--ink-faint);\n}\n.fo-card-by { color: var(--ink-soft); }\n\n/* tall cards get a slightly taller crop for editorial rhythm */\n.fo-card--tall .fo-card-img { aspect-ratio: 3 / 4; }\n\n/* LOAD MORE */\n.fo-more-wrap { display: flex; justify-content: center; margin-top: clamp(2.5rem, 5vw, 4rem); }\n.fo-more {\n  font: inherit; font-size: 0.95rem; font-weight: 500;\n  color: var(--ink); background: transparent;\n  border: 1px solid var(--ink); border-radius: 999px;\n  padding: 0.85rem 2rem; cursor: pointer;\n  transition: color 0.3s, background 0.3s, transform 0.3s;\n}\n.fo-more:hover { background: var(--ink); color: var(--paper); transform: translateY(-2px); }\n\n/* NEWSLETTER PROMO */\n.fo-promo { background: var(--ink); color: var(--paper); }\n.fo-promo-inner {\n  max-width: 1320px; margin: 0 auto;\n  display: grid; grid-template-columns: 1.1fr 0.9fr; gap: clamp(1.8rem, 4vw, 4rem);\n  align-items: center;\n  padding: clamp(3rem, 6vw, 5.5rem) clamp(1.1rem, 4vw, 3.5rem);\n}\n.fo-promo .fo-kicker--ink { color: #b9b2a8; }\n.fo-promo-title {\n  font-family: var(--font-fraunces), Georgia, serif;\n  font-weight: 460;\n  font-size: clamp(1.8rem, 3.4vw, 2.9rem);\n  line-height: 1.05; letter-spacing: -0.02em;\n  margin: 0 0 1rem; max-width: 24rem;\n}\n.fo-promo-dek { color: #c9c3ba; font-size: 1.02rem; margin: 0; max-width: 32rem; }\n.fo-promo-form { width: 100%; }\n.fo-field-label {\n  display: block; font-size: 0.8rem; color: #b9b2a8; margin-bottom: 0.6rem;\n}\n.fo-field-row { display: flex; gap: 0.6rem; }\n.fo-field {\n  flex: 1; font: inherit; font-size: 0.95rem;\n  background: transparent; color: var(--paper);\n  border: 1px solid rgba(247, 245, 241, 0.3);\n  border-radius: 999px; padding: 0.85rem 1.3rem;\n  transition: border-color 0.3s;\n}\n.fo-field::placeholder { color: #8a827a; }\n.fo-field:focus { outline: none; border-color: var(--red); }\n.fo-field-btn {\n  font: inherit; font-size: 0.95rem; font-weight: 500;\n  background: var(--red); color: #fff; border: none;\n  border-radius: 999px; padding: 0.85rem 1.6rem; cursor: pointer;\n  transition: background 0.3s, transform 0.3s; white-space: nowrap;\n}\n.fo-field-btn:hover { background: var(--red-deep); transform: translateY(-2px); }\n.fo-field-note { color: #8a827a; font-size: 0.82rem; margin: 0.9rem 0 0; }\n@media (max-width: 780px) {\n  .fo-promo-inner { grid-template-columns: 1fr; }\n  .fo-field-row { flex-direction: column; }\n  .fo-field-btn { width: 100%; }\n}\n\n/* EDITOR'S PICKS */\n.fo-picks { max-width: 1320px; margin: 0 auto; padding: clamp(3rem, 6vw, 5rem) clamp(1.1rem, 4vw, 3.5rem); }\n.fo-picks-track {\n  display: grid; grid-template-columns: repeat(4, 1fr); gap: clamp(1.2rem, 2vw, 1.8rem);\n  margin-top: 2rem;\n}\n.fo-pick { text-decoration: none; color: inherit; display: block; }\n.fo-pick-img {\n  aspect-ratio: 5 / 4; background-size: cover; background-position: center;\n  border-radius: 4px; overflow: hidden;\n  filter: grayscale(0.12); transition: filter 0.5s, transform 0.6s cubic-bezier(0.16, 1, 0.3, 1);\n}\n.fo-pick:hover .fo-pick-img { filter: grayscale(0); transform: translateY(-4px); }\n.fo-pick-body { padding-top: 0.9rem; }\n.fo-pick-cat { font-size: 0.78rem; font-weight: 600; color: var(--red); }\n.fo-pick-title {\n  font-family: var(--font-fraunces), Georgia, serif;\n  font-weight: 500; font-size: 1.08rem; line-height: 1.18; letter-spacing: -0.01em;\n  margin: 0.35rem 0 0.4rem;\n}\n.fo-pick-by { font-size: 0.82rem; color: var(--ink-faint); }\n@media (max-width: 820px) { .fo-picks-track { grid-template-columns: repeat(2, 1fr); } }\n@media (max-width: 460px) { .fo-picks-track { grid-template-columns: 1fr; } }\n\n/* FOOTER */\n.fo-footer { border-top: 1px solid var(--ink); background: var(--paper); }\n.fo-footer-top {\n  max-width: 1320px; margin: 0 auto;\n  display: grid; grid-template-columns: 1.2fr 1.8fr; gap: clamp(2rem, 5vw, 4rem);\n  padding: clamp(3rem, 6vw, 4.5rem) clamp(1.1rem, 4vw, 3.5rem) clamp(2rem, 4vw, 3rem);\n}\n.fo-wordmark--footer { font-size: 2rem; display: inline-block; margin-bottom: 1rem; }\n.fo-footer-blurb { color: var(--ink-soft); font-size: 0.95rem; margin: 0; max-width: 24rem; }\n.fo-footer-cols { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1.5rem; }\n.fo-footer-col { display: flex; flex-direction: column; gap: 0.6rem; }\n.fo-col-head { font-size: 0.78rem; color: var(--ink-faint); margin-bottom: 0.3rem; }\n.fo-footer-col a {\n  font-size: 0.92rem; color: var(--ink-soft); text-decoration: none;\n  transition: color 0.25s; width: fit-content;\n}\n.fo-footer-col a:hover { color: var(--red); }\n.fo-footer-base {\n  max-width: 1320px; margin: 0 auto;\n  display: flex; align-items: center; justify-content: space-between; flex-wrap: wrap; gap: 0.6rem;\n  padding: 1.4rem clamp(1.1rem, 4vw, 3.5rem);\n  border-top: 1px solid var(--line);\n  font-size: 0.82rem; color: var(--ink-faint);\n}\n@media (max-width: 760px) {\n  .fo-footer-top { grid-template-columns: 1fr; }\n  .fo-footer-cols { grid-template-columns: repeat(3, 1fr); }\n}\n@media (max-width: 480px) { .fo-footer-cols { grid-template-columns: 1fr 1fr; } }\n\n/* REDUCED MOTION — show everything, kill transforms/wipes */\n.folio-root.is-reduced .fo-cover-img,\n.folio-root.is-reduced .fo-card-img { transition: none; transform: none; }\n.folio-root.is-reduced .fo-cover-mask,\n.folio-root.is-reduced .fo-card-wipe { display: none; }\n.folio-root.is-reduced .fo-card { opacity: 1; transform: none; transition: none; }\n@media (prefers-reduced-motion: reduce) {\n  .folio-root .fo-cover-img,\n  .folio-root .fo-card-img,\n  .folio-root .fo-card,\n  .folio-root .fo-card-float,\n  .folio-root .fo-cover-cta,\n  .folio-root .fo-more,\n  .folio-root .fo-pick-img,\n  .folio-root .fo-field-btn { transition: none !important; }\n  .folio-root .fo-cover-mask,\n  .folio-root .fo-card-wipe { display: none !important; }\n  .folio-root .fo-card { opacity: 1 !important; transform: none !important; }\n  .folio-root .fo-cover-img { transform: none !important; }\n}\n`\n","type":"registry:page"}],"assets":[{"url":"https://ui.artbloom.tech/r/asset/templates/folio/assets/cover.png","target":"public/folio/cover.png","bytes":2310581},{"url":"https://ui.artbloom.tech/r/asset/templates/folio/assets/a1.png","target":"public/folio/a1.png","bytes":2733994},{"url":"https://ui.artbloom.tech/r/asset/templates/folio/assets/a2.png","target":"public/folio/a2.png","bytes":2772591},{"url":"https://ui.artbloom.tech/r/asset/templates/folio/assets/a3.png","target":"public/folio/a3.png","bytes":2918288},{"url":"https://ui.artbloom.tech/r/asset/templates/folio/assets/a4.png","target":"public/folio/a4.png","bytes":1774975},{"url":"https://ui.artbloom.tech/r/asset/templates/folio/assets/a5.png","target":"public/folio/a5.png","bytes":2461991}],"meta":{"kind":"templates","categories":["editorial"],"docs":"https://ui.artbloom.tech/artbloom/templates/folio"}}