diff --git a/alembic/versions/014_add_creator_avatar.py b/alembic/versions/014_add_creator_avatar.py new file mode 100644 index 0000000..0358db2 --- /dev/null +++ b/alembic/versions/014_add_creator_avatar.py @@ -0,0 +1,24 @@ +"""Add avatar columns to creators table. + +Revision ID: 014_add_creator_avatar +Revises: 013_add_search_log +""" +from alembic import op +import sqlalchemy as sa + +revision = "014_add_creator_avatar" +down_revision = "013_add_search_log" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + op.add_column("creators", sa.Column("avatar_url", sa.String(1000), nullable=True)) + op.add_column("creators", sa.Column("avatar_source", sa.String(50), nullable=True)) + op.add_column("creators", sa.Column("avatar_fetched_at", sa.TIMESTAMP(), nullable=True)) + + +def downgrade() -> None: + op.drop_column("creators", "avatar_fetched_at") + op.drop_column("creators", "avatar_source") + op.drop_column("creators", "avatar_url") diff --git a/backend/models.py b/backend/models.py index c7c0eea..6b6e497 100644 --- a/backend/models.py +++ b/backend/models.py @@ -103,6 +103,9 @@ class Creator(Base): slug: Mapped[str] = mapped_column(String(255), unique=True, nullable=False) genres: Mapped[list[str] | None] = mapped_column(ARRAY(String), nullable=True) folder_name: Mapped[str] = mapped_column(String(255), nullable=False) + avatar_url: Mapped[str | None] = mapped_column(String(1000), nullable=True) + avatar_source: Mapped[str | None] = mapped_column(String(50), nullable=True) + avatar_fetched_at: Mapped[datetime | None] = mapped_column(nullable=True) view_count: Mapped[int] = mapped_column(Integer, default=0, server_default="0") hidden: Mapped[bool] = mapped_column(default=False, server_default="false") created_at: Mapped[datetime] = mapped_column( diff --git a/frontend/src/App.css b/frontend/src/App.css index d3b7286..75b919b 100644 --- a/frontend/src/App.css +++ b/frontend/src/App.css @@ -2032,10 +2032,8 @@ a.app-footer__repo:hover { /* ── Table of Contents ────────────────────────────────────────────────────── */ .technique-toc { - background: var(--color-bg-surface); - border: 1px solid var(--color-border); - border-radius: 0.5rem; - padding: 1rem 1.25rem; + border-left: 2px solid var(--color-accent); + padding: 0 0 0 1rem; margin-bottom: 1.5rem; } @@ -2052,56 +2050,52 @@ a.app-footer__repo:hover { list-style: none; padding: 0; margin: 0; - counter-reset: toc-section; } .technique-toc__item { - counter-increment: toc-section; - margin-bottom: 0.25rem; + margin-bottom: 0.125rem; } .technique-toc__link { - color: var(--color-accent); + display: block; + color: var(--color-text-secondary); text-decoration: none; font-size: 0.875rem; line-height: 1.6; -} - -.technique-toc__link::before { - content: counter(toc-section) ". "; - color: var(--color-text-muted); + padding: 0.125rem 0.5rem; + border-radius: 0.25rem; + transition: background 150ms ease, color 150ms ease; } .technique-toc__link:hover { - text-decoration: underline; + background: var(--color-accent-subtle); + color: var(--color-accent); } .technique-toc__sublist { list-style: none; - padding-left: 1.25rem; + padding-left: 1rem; margin: 0.125rem 0 0.25rem; - counter-reset: toc-sub; } .technique-toc__subitem { - counter-increment: toc-sub; + margin-bottom: 0; } .technique-toc__sublink { - color: var(--color-text-secondary); + display: block; + color: var(--color-text-muted); text-decoration: none; font-size: 0.8125rem; line-height: 1.6; -} - -.technique-toc__sublink::before { - content: counter(toc-section) "." counter(toc-sub) " "; - color: var(--color-text-muted); + padding: 0.125rem 0.5rem; + border-radius: 0.25rem; + transition: background 150ms ease, color 150ms ease; } .technique-toc__sublink:hover { + background: var(--color-accent-subtle); color: var(--color-accent); - text-decoration: underline; } /* ── V2 subsections ───────────────────────────────────────────────────────── */ diff --git a/frontend/src/components/TableOfContents.tsx b/frontend/src/components/TableOfContents.tsx index 7bf9edc..70d18d5 100644 --- a/frontend/src/components/TableOfContents.tsx +++ b/frontend/src/components/TableOfContents.tsx @@ -23,8 +23,8 @@ export default function TableOfContents({ sections }: TableOfContentsProps) { return ( ); } diff --git a/frontend/src/pages/TechniquePage.tsx b/frontend/src/pages/TechniquePage.tsx index 570593e..b97e615 100644 --- a/frontend/src/pages/TechniquePage.tsx +++ b/frontend/src/pages/TechniquePage.tsx @@ -421,7 +421,6 @@ export default function TechniquePage() {
{displayFormat === "v2" && Array.isArray(displaySections) ? ( <> - {(displaySections as BodySectionV2[]).map((section) => { const sectionSlug = slugify(section.heading); return ( @@ -469,6 +468,10 @@ export default function TechniquePage() {
+ {/* Table of Contents — v2 pages only */} + {displayFormat === "v2" && Array.isArray(displaySections) && (displaySections as BodySectionV2[]).length > 0 && ( + + )} {/* Key moments (always from live data — not versioned) */} {technique.key_moments.length > 0 && (