diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..e35f7a0 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,13 @@ +{ + "permissions": { + "allow": [ + "Bash(docker exec:*)", + "Bash(docker logs:*)", + "Bash(docker-compose:*)", + "Bash(docker volume rm:*)", + "Bash(composer install:*)" + ], + "deny": [], + "ask": [] + } +} \ No newline at end of file diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..fe5d2d4 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,32 @@ +name: CI +on: + push: + branches: + - main + pull_request: +jobs: + build: + runs-on: ubuntu-latest + services: + mysql: + image: mysql:5.7 + env: + MYSQL_ROOT_PASSWORD: password + MYSQL_DATABASE: wordpress + ports: + - 3306:3306 + options: --health-cmd="mysqladmin ping" --health-interval=10s --health-timeout=5s --health-retries=3 + steps: + - uses: actions/checkout@v3 + - name: Setup PHP + uses: shivammathur/setup-php@v2 + with: + php-version: '8.2' + tools: composer, phpcs, phpunit + extensions: mysqli + - name: Install dependencies + run: composer install --prefer-dist --no-progress + - name: Run PHPCS + run: vendor/bin/phpcs --standard=phpcs.xml --ignore=vendor postsecret/wp-content + - name: Run PHPUnit + run: vendor/bin/phpunit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..4c1da9b --- /dev/null +++ b/.gitignore @@ -0,0 +1,111 @@ +# WordPress Core +# Note: wp-content is NOT ignored since this is a theme/plugin dev repo +/wp-admin/ +/wp-includes/ +/wp-*.php +/xmlrpc.php +/readme.html +/license.txt + +# WordPress Config +wp-config.php +wp-config-local.php + +# Composer +/vendor/ +composer.lock + +# Node.js (if used for asset building) +node_modules/ +package-lock.json +yarn.lock + +# Build artifacts +*.map +*.min.css +*.min.js + +# Operating System +.DS_Store +.DS_Store? +._* +.Spotlight-V100 +.Trashes +ehthumbs.db +Thumbs.db +Desktop.ini + +# IDE/Editor +.vscode/ +.idea/ +*.sublime-project +*.sublime-workspace +*.swp +*.swo +*~ +.phpintel/ +.phpunit.result.cache + +# Logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +error_log +debug.log + +# Docker +docker-compose.override.yml + +# Database +*.sql +*.sql.gz +/db_data/ + +# Uploads and cache +wp-content/uploads/ +wp-content/cache/ +wp-content/upgrade/ +wp-content/backup-db/ +wp-content/backups/ +wp-content/blogs.dir/ +wp-content/updraft/ +wp-content/ai1wm-backups/ + +# Plugins (keep only custom plugins) +wp-content/plugins/akismet/ +wp-content/plugins/hello.php +# Keep postsecret-admin plugin + +# Themes (keep only custom themes) +wp-content/themes/twenty*/ +# Keep postsecret theme + +# WordPress Updates +wp-content/upgrade-temp-backup/ + +# Testing +.phpunit.result.cache +/tests/_output/ +/tests/_support/_generated/ + +# Environment +.env +.env.local +.env.*.local + +# Temporary files +*.tmp +*.temp +.cache/ + +# Backfill/batch processing +wp-content/quarantine/ +wp-content/backfill-logs/ + +# Security +*.pem +*.key +*.cert +credentials.json +secrets.json diff --git a/CLAUDE.md b/CLAUDE.md new file mode 100644 index 0000000..6eb16e5 --- /dev/null +++ b/CLAUDE.md @@ -0,0 +1,318 @@ +# CLAUDE.md + +This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. + +## Project Overview + +PostSecret is a WordPress-based archive, search, and moderation system for the PostSecret collection (~1M postcards). It consolidates public discovery, moderation, and data management into a single WordPress stack. + +**Core Components:** +- **Custom WordPress Theme** (`wp-content/themes/postsecret/`) - Public-facing archive, search, and detail pages +- **Admin Plugin** (`wp-content/plugins/postsecret-admin/`) - Moderation queues, taxonomy governance, audit logging, backfill jobs, and settings +- **MySQL Database** - Canonical secret records with full-text and tag indexes + +**Product Principles:** +- Product-first, single-stack, accessible by default, privacy-preserving +- Future-ready: schema and UI accept similarity search module (Phase 2) without re-platforming +- Safety and policy at the center: public-safe defaults, PII avoidance, server-side enforcement + +## Development Setup + +**Start the environment:** +```bash +docker-compose up -d +``` +WordPress runs at `http://localhost:8080`. The `wp-content` directory is mounted for live development. + +**Install PHP dependencies:** +```bash +composer install +``` + +**Run tests:** +```bash +composer test +# or directly: +vendor/bin/phpunit +``` + +**Run code style checks:** +```bash +vendor/bin/phpcs +``` + +**CI runs automatically** on push/PR and executes both PHPCS and PHPUnit tests. + +## Code Architecture + +### Plugin Architecture (`postsecret-admin`) + +**Namespace:** `PostSecret\Admin\` + +**Bootstrap:** `postsecret-admin.php` initializes all route classes on `plugins_loaded` hook. + +**Key Components:** +- **Routes/** - Request handlers for admin endpoints (Search, Review, Taxonomy, Backfill, Settings) +- **Services/** - Business logic layer: + - `SearchService` - Full-text + tag search (tokenized, stemmed) + - `ModerationService` - Queue management and approval workflows + - `TaxonomyService` - Tag merge/alias operations + - `LoggingService` - Audit trail (actor, action, timestamp, context) + - `ConfigService` - Policy thresholds and settings +- **Model/** - DTOs like `Secret` (id, title, content, tags) +- **Util/** - Sanitization (`Sanitize`) and capability checks (`Caps`) +- **CLI/** - WP-CLI commands via `class-ps-cli.php` + - `wp postsecret backfill --batch= --rate=` +- **migrations/** - Database schema files (e.g., `001_init.php`) + +**Database Tables:** +- `ps_classification` - OCR text, descriptors, confidence scores, moderation state +- `ps_audit_log` - Complete audit trail of privileged actions (append-only, immutable) +- `ps_tag_alias` - Tag normalization (alias → canonical) +- `ps_backfill_job`, `ps_backfill_item` - Backfill progress tracking, checkpoints, error quarantine + +**Canonical Secret Record Structure:** +Each Secret has: image pointers, extracted/approved text, tags, media descriptors (art/font/media), moderation state (pending/needs_review/approved/published/unpublished/flagged), confidence scores, provenance metadata. + +### Theme Architecture (`postsecret`) + +**Templates:** +- `front-page.php` - Homepage +- `archive-secrets.php` - Browse view +- `search.php` - Search results +- `single-secret.php` - Detail page +- `parts/card.php` - Reusable secret card component + +**Includes (`inc/`):** +- `a11y.php` - WCAG 2.2 AA accessibility enhancements +- `seo.php` - Meta tags and structured data +- `routing.php` - Custom rewrite rules + +**Assets:** +- `assets/css/style.css` - Compiled styles +- `assets/js/main.js` - Frontend interactions + +### Data Flow + +**Public Search:** +1. User submits query + optional tag filters +2. Theme calls `SearchService->search()` +3. Service executes tokenized/stemmed MySQL full-text query with tag JOIN +4. Results sorted by relevance (default) or recency +5. Paginated results rendered via theme templates + +**Admin Moderation:** +1. Moderator accesses queue via `ReviewRoute` +2. `ModerationService` fetches items by state (needs_review, low_confidence, flagged, published) +3. Moderator reviews item with confidence indicators and policy signals +4. Actions: approve/publish/unpublish/re-review/edit tags (with capability + nonce checks) +5. `LoggingService` records action (actor, target, before/after, timestamp, outcome) to append-only audit log +6. State transition committed; caches invalidated + +**Backfill (Historical Import ~1M Secrets):** +1. Initiated via WP-CLI (`wp postsecret backfill`) or admin UI (`BackfillRoute`) +2. Jobs are resumable (checkpoint per batch), idempotent (hash/signature), bounded retries +3. Schema validation; per-item error capture → quarantine table with reason +4. **Auto-throttling:** Pauses if p95 search > 1.2s for 15 minutes; resumes when ≤ 600ms for 30 min +5. Progress metrics: processed count, failed count, ETA, reconciliation against source +6. Quality gates: Policy-driven gating for NSFW/self-harm; audit trail per batch + +## Standards & Requirements + +**Code Style:** WordPress Coding Standards (WPCS) enforced via PHPCS. Configuration in `phpcs.xml`. + +**Security & Privacy:** +- All admin actions require capability checks (via `Caps` utility) - `current_user_can()` on every route/action +- Nonce validation required for all state-changing operations (CSRF protection) +- Server-side policy gates enforce content safety (NSFW/self-harm thresholds) +- No PII in public-facing queries or responses; logs avoid raw PII/secrets +- Input sanitization + output escaping; HTML blocked in tags/notes +- Soft rate limiting on search and admin bulk endpoints +- API keys/config stored server-side only; never exposed client-side + +**Accessibility:** All UIs target WCAG 2.2 AA with: +- Keyboard-complete navigation +- Visible focus indicators +- Correct ARIA roles and labels +- Screen reader tested flows + +**i18n:** All strings wrapped in translation functions (`__()`, `_e()`, `esc_html__()`). Text domain: `postsecret` (theme), `postsecret-admin` (plugin). + +**Performance Targets:** +- p95 search ≤ 600 ms (server processing for text+tag queries) +- Cold cache ≤ 1.2 s (triggers alert if sustained) +- Mobile time-to-first-useful-result ≤ 2.5 s (4G) +- Core Web Vitals: LCP/INP/CLS in "Good" ranges +- Uptime ≥ 99.9%; ≤ 0.25% 5xx error rate on public search endpoints +- Admin queue load p95 ≤ 400 ms; item open p95 ≤ 300 ms + +**Caching Strategy:** +- Object cache for query fragments +- Short-TTL page/query caches (vary by q|tags|sort|page) +- HTTP caching headers on public routes +- Cache busting on publish/unpublish and tag merges +- Image lazy-load + responsive srcset + +## Testing + +**Unit Tests:** Located in `wp-content/plugins/postsecret-admin/tests/` + +**Test Bootstrap:** `tests/bootstrap.php` loads WordPress test environment + +**Run single test:** +```bash +vendor/bin/phpunit --filter=test_name +``` + +## Roles & Capabilities + +**Admin Role (MVP):** +- Full editorial control: review queues, approve/publish/unpublish, re-review, edit tags +- Taxonomy governance: merge/alias/delete tags +- Settings: configure confidence thresholds, policy gates +- Audit logs: view/export all privileged actions + +**Custom Capabilities (via `Caps` utility):** +- `ps.view_admin` - Access to PostSecret admin screens +- `ps.review.queue` - View triage queues & item details +- `ps.review.act` - Approve/reject/re-review items +- `ps.publish` - Publish/unpublish items +- `ps.tags.merge` - Merge/alias/delete tags +- `ps.logs.view` - View/export audit logs + +**Access Control Principles:** +- Least privilege: assign minimal capabilities needed +- Separation of duties: publishing, taxonomy merges, settings are distinct powers +- Explicit gating: all admin actions require both capability checks AND nonces +- Auditability: every privileged action logged with actor, timestamp, target, outcome +- Deny by default: `current_user_can()` checks on every route/action + +## Common Development Patterns + +**Adding a new moderation queue:** +1. Add queue state constant in `ModerationService` +2. Create SQL query method in service +3. Add route handler in `ReviewRoute` +4. Update admin UI to link to new queue + +**Adding a new WP-CLI command:** +1. Add method to `PS_CLI` class in `cli/class-ps-cli.php` +2. Register command in `register()` method +3. Document synopsis with `@synopsis` docblock + +**Database migrations:** +1. Create new file in `migrations/` (e.g., `002_description.php`) +2. Implement `up()` function with SQL +3. Use `dbDelta()` for table creation/updates +4. Trigger via WP-CLI or admin migration UI + +**Adding a new service:** +1. Create class in `src/Services/` +2. Use namespace `PostSecret\Admin\Services` +3. Inject via constructor or use singleton pattern +4. Call from route handlers + +**Taxonomy operations (merge/alias):** +1. Use `TaxonomyService->merge()` or `->alias()` methods +2. Mark deprecated tag as alias pointing to canonical +3. Reindex affected Secrets asynchronously +4. Log operation to audit trail with actor and rationale +5. Target: ≤1% duplicate/orphan tag operations + +**Handling bulk operations:** +1. Validate capability + nonce before processing +2. Preview affected item count before commit +3. For >100 items, require type-to-confirm from user +4. Process in batches; record progress +5. On partial failure: complete successful items, report failures with CSV export +6. Single audit entry referencing all target IDs + +## Key Files Reference + +- Plugin entry: `wp-content/plugins/postsecret-admin/postsecret-admin.php` +- Theme entry: `wp-content/themes/postsecret/functions.php` +- WP-CLI commands: `wp-content/plugins/postsecret-admin/cli/class-ps-cli.php` +- Database schema: `wp-content/plugins/postsecret-admin/migrations/001_init.php` +- Search logic: `wp-content/plugins/postsecret-admin/src/Services/SearchService.php` +- Moderation flow: `wp-content/plugins/postsecret-admin/src/Services/ModerationService.php` +- Audit logging: `wp-content/plugins/postsecret-admin/src/Services/LoggingService.php` + +## Public UI Requirements + +**Search Behavior:** +- Input: debounced 250-400ms; Enter submits immediately; Esc clears text focus +- Parsing: plain keywords, case-insensitive, ASCII folding (no boolean operators required at MVP) +- Scope: queries run against approved text fields (OCR/model text) +- Facets: multi-select tags with counts; selected tags shown as removable chips +- Sorting: relevance (default), recency +- Pagination: server-side, deterministic ordering; 24 items/page (desktop), 12 (mobile) +- URL state: all search states (query + facets + sort + page) encoded in URL and shareable + +**Result Cards:** +- Image thumbnail (aspect-aware, lazy-loaded) with alt text from descriptors +- Key tags (up to 3 chips; overflow "+N") +- Text excerpt (first ~140 chars of approved text; ellipsis if truncated) +- Safe indicators (e.g., "Content advisory" icon if applicable) +- Click target: entire card opens detail view + +**Detail View:** +- Large image with zoom/lightbox; alt text provided +- Tags, art/font/media descriptors, orientation +- Approved extracted text; language label if non-English +- Postmark/ingest date (if public-safe), canonical link +- Placeholder area for Phase 2 "Find similar" module + +**Empty/Error States:** +- Zero results: guidance ("Try fewer tags", "Check spelling") + top tags +- Partial results: non-blocking alert if facet fails; retry affordance +- Errors: friendly message + retry; no stack traces; status logged server-side + +## Admin UI Requirements + +**Queues:** +- Views: Needs Review, Low Confidence, Flagged, Published (read-only) +- Display: paginated tables/grids with thumbnail, key tags, confidence badge, review status, last action/actor, updated time +- Filters: tags (multi-select), status, confidence range slider, date range, text contains +- Sorting: updated time (default), confidence, recency +- Batch size: 25 per page (configurable) + +**Item Detail Panel:** +- Full image (zoom), approved text, language, descriptors (tags, art/font/media) +- Signals: confidence (overall + by-field), moderation labels, NSFW/self-harm flags, policy notes +- History: last 5 actions (actor, timestamp, summary); link to full audit log + +**Editorial Actions:** +- Single-item: Approve, Publish, Unpublish, Send to Re-review, Edit/Add Tags, Edit Text (approved field), Add Note (internal) +- Guards: confirmation dialogs for Publish/Unpublish; policy interstitials for flagged content +- Undo: 30-second inline undo for Publish/Unpublish where feasible +- Provenance: all edits record actor, timestamp, rationale (optional note) + +**Workflow States & Transitions:** +- States: `pending`, `needs_review`, `approved`, `published`, `unpublished`, `flagged` +- Invalid transitions blocked with guidance (e.g., `published` → `approved` requires `unpublish` first) +- Auto-routing: items with sensitive labels or low confidence land in Needs Review + +**Settings:** +- Confidence threshold for Low Confidence queue +- Policy toggles for NSFW/self-harm gating +- Default public visibility rules +- Preview: shows projected queue deltas before save +- Rollback: one-click revert to previous settings version + +## Documentation + +- `docs/DEV_SETUP.md` - Detailed setup instructions +- `docs/MODERATION_GUIDE.md` - Queue review workflows +- `docs/TAG_GOVERNANCE.md` - Taxonomy management guidelines +- `README.md` - High-level project overview and architecture + +## Future: Phase 2 Similarity Search + +**Out of MVP scope** - designed for pluggable integration without re-platforming: + +- Entry points: "Similar Secrets" module on detail page (lazy-loaded); "Find similar" button on result cards +- Signals: visual embedding similarity, text embedding similarity, tag overlap boost, freshness +- Ranking: cosine distance on embeddings; tag overlap and moderation safety boosts; near-duplicate suppression +- Storage: embeddings stored as artifacts linked to canonical Secret record (model name, dimension, timestamp) +- Target: p95 ≤ 900 ms for top-K similarity request; ≥15% CTR on detail pages +- Safety: only public-safe items are candidates; respects all policy gates \ No newline at end of file diff --git a/README.md b/README.md index a2ff6a1..740bd8c 100644 --- a/README.md +++ b/README.md @@ -2,6 +2,8 @@ Fast, accessible browsing of the entire PostSecret collection, built on WordPress and MySQL. Public users get full-text search and tag filters; moderators get review queues, publishing controls, taxonomy tools, and an audit trail—privacy and safety first. +**[📋 View Project Kanban Board](https://tree.taiga.io/project/flatts-postsecret/kanban)** + --- ## Why this exists diff --git a/composer.json b/composer.json new file mode 100644 index 0000000..b9e0cdd --- /dev/null +++ b/composer.json @@ -0,0 +1,17 @@ +{ + "name": "postsecret/project", + "type": "project", + "require": {}, + "autoload": { + "psr-4": { + "PostSecret\\\\": "wp-content/plugins/postsecret-admin/src/" + } + }, + "require-dev": { + "phpunit/phpunit": "^9", + "squizlabs/php_codesniffer": "^3" + }, + "scripts": { + "test": "phpunit" + } +} diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..ee715fb --- /dev/null +++ b/docker-compose.yml @@ -0,0 +1,68 @@ +services: + wordpress: + image: wordpress:latest + container_name: postsecret_wordpress + restart: unless-stopped + ports: + - "8080:80" + environment: + WORDPRESS_DB_HOST: db:3306 + WORDPRESS_DB_USER: wordpress + WORDPRESS_DB_PASSWORD: wordpress + WORDPRESS_DB_NAME: wordpress + WP_HOME: http://localhost:8080 + WP_SITEURL: http://localhost:8080 + WORDPRESS_DEBUG: 1 + WORDPRESS_CONFIG_EXTRA: | + define('WP_DEBUG_LOG', true); + define('WP_DEBUG_DISPLAY', false); + define('SCRIPT_DEBUG', true); + volumes: + - ./wp-content:/var/www/html/wp-content + - wordpress_data:/var/www/html + depends_on: + db: + condition: service_healthy + healthcheck: + test: ["CMD", "curl", "-f", "http://localhost"] + interval: 30s + timeout: 10s + retries: 3 + start_period: 40s + + db: + image: mysql:8.0 + container_name: postsecret_db + restart: unless-stopped + command: --default-authentication-plugin=mysql_native_password + environment: + MYSQL_DATABASE: wordpress + MYSQL_USER: wordpress + MYSQL_PASSWORD: wordpress + MYSQL_ROOT_PASSWORD: rootpassword + volumes: + - db_data:/var/lib/mysql + healthcheck: + test: ["CMD", "mysqladmin", "ping", "-h", "localhost", "-u", "wordpress", "-pwordpress"] + interval: 10s + timeout: 5s + retries: 5 + start_period: 30s + + phpmyadmin: + image: phpmyadmin:5.2 + container_name: postsecret_phpmyadmin + restart: unless-stopped + ports: + - "8081:80" + environment: + PMA_HOST: db + PMA_USER: wordpress + PMA_PASSWORD: wordpress + UPLOAD_LIMIT: 100M + depends_on: + - db + +volumes: + db_data: + wordpress_data: diff --git a/docs/DEV_SETUP.md b/docs/DEV_SETUP.md new file mode 100644 index 0000000..c10dd6e --- /dev/null +++ b/docs/DEV_SETUP.md @@ -0,0 +1,42 @@ +# Development Setup + +This document describes how to get the PostSecret project up and running locally. + +## Prerequisites + +- Docker and Docker Compose installed. +- PHP 8.2 with Composer if you intend to run unit tests outside of Docker. + +## Steps + +1. **Clone the repository**: + + ```bash + git clone https://example.com/postsecret.git + cd postsecret + ``` + +2. **Bring up the environment**: + + Use Docker Compose to start WordPress and MySQL: + + ```bash + docker-compose up -d + ``` + + WordPress will be available at . The `wp-content` directory is mounted so you can develop the theme and plugin locally. + +3. **Install PHP dependencies**: + + ```bash + composer install + ``` + +4. **Run tests and coding standards**: + + ```bash + composer test + vendor/bin/phpcs + ``` + +Refer to the other documents in the `docs/` directory for guidelines on moderation and tag governance. diff --git a/docs/MODERATION_GUIDE.md b/docs/MODERATION_GUIDE.md new file mode 100644 index 0000000..b26a159 --- /dev/null +++ b/docs/MODERATION_GUIDE.md @@ -0,0 +1,22 @@ +# Moderation Guide + +This guide outlines recommended practices for reviewing and publishing secrets through the PostSecret admin plugin. + +## Review Queues + +The admin plugin provides several queues: + +- **Needs Review** – Newly ingested secrets awaiting moderation. +- **Low Confidence** – Items flagged by automated classifiers for uncertain content. +- **Flagged** – Items flagged by users or automated systems for potential issues. +- **Published** – Secrets that have been approved and are publicly visible. + +## Review Process + +1. Open the **Needs Review** queue and select an item. +2. Read the secret text and inspect the attached image. +3. Assign tags where appropriate. +4. If everything looks good, approve the item to move it to **Published**. +5. If the item is problematic, unpublish it or move it to **Flagged** for further review. + +Always be respectful and mindful of the sensitive nature of anonymous secrets. diff --git a/docs/TAG_GOVERNANCE.md b/docs/TAG_GOVERNANCE.md new file mode 100644 index 0000000..19a6074 --- /dev/null +++ b/docs/TAG_GOVERNANCE.md @@ -0,0 +1,15 @@ +# Tag Governance + +Tagging is critical for searchability and categorization. This document outlines guidelines for managing tags within the system. + +## Creating Tags + +Tags should be concise and descriptive. Avoid creating near-duplicates or overly specific tags when a broader term would suffice. + +## Merging and Aliasing + +When multiple tags represent the same concept, moderators can merge them. Use aliases to point variant spellings or synonyms to a canonical tag. + +## Reserved Tags + +Some tags may be reserved for system use (e.g., `featured`, `nsfw`). Do not repurpose these without consulting the development team. diff --git a/phpcs.xml b/phpcs.xml new file mode 100644 index 0000000..1a230fe --- /dev/null +++ b/phpcs.xml @@ -0,0 +1,7 @@ + + + Code style rules for the PostSecret project. + + wp-content/themes/postsecret + wp-content/plugins/postsecret-admin + diff --git a/phpunit.xml b/phpunit.xml new file mode 100644 index 0000000..a46fa2e --- /dev/null +++ b/phpunit.xml @@ -0,0 +1,10 @@ + + + + + wp-content/plugins/postsecret-admin/tests + + + diff --git a/wp-content/index.php b/wp-content/index.php new file mode 100644 index 0000000..6220032 --- /dev/null +++ b/wp-content/index.php @@ -0,0 +1,2 @@ +] [--rate=] + * + * @param array $args Positional arguments. + * @param array $assoc_args Associative arguments. + */ + public static function backfill( $args, $assoc_args ) { + $batch = isset( $assoc_args['batch'] ) ? intval( $assoc_args['batch'] ) : 100; + $rate = isset( $assoc_args['rate'] ) ? intval( $assoc_args['rate'] ) : 10; + + // TODO: Implement backfill logic using BackfillService. + WP_CLI::success( "Backfill started with batch {$batch} and rate {$rate}." ); + } +} + +// Register commands if WP_CLI is defined. +if ( defined( 'WP_CLI' ) && WP_CLI ) { + \PostSecret\Admin\CLI\PS_CLI::register(); +} diff --git a/wp-content/plugins/postsecret-admin/composer.json b/wp-content/plugins/postsecret-admin/composer.json new file mode 100644 index 0000000..091bade --- /dev/null +++ b/wp-content/plugins/postsecret-admin/composer.json @@ -0,0 +1,13 @@ +{ + "name": "postsecret/postsecret-admin", + "description": "Admin tools for managing the PostSecret archive", + "type": "wordpress-plugin", + "autoload": { + "psr-4": { + "PostSecret\\Admin\\": "src/" + } + }, + "require": { + "php": ">=8.0" + } +} diff --git a/wp-content/plugins/postsecret-admin/languages/readme.txt b/wp-content/plugins/postsecret-admin/languages/readme.txt new file mode 100644 index 0000000..f91328c --- /dev/null +++ b/wp-content/plugins/postsecret-admin/languages/readme.txt @@ -0,0 +1 @@ +This directory is reserved for translation files (.po, .mo) used by the plugin. diff --git a/wp-content/plugins/postsecret-admin/migrations/001_init.php b/wp-content/plugins/postsecret-admin/migrations/001_init.php new file mode 100644 index 0000000..2809ba7 --- /dev/null +++ b/wp-content/plugins/postsecret-admin/migrations/001_init.php @@ -0,0 +1,56 @@ +get_charset_collate(); + + $table_name_classification = $wpdb->prefix . 'ps_classification'; + $table_name_audit = $wpdb->prefix . 'ps_audit_log'; + $table_name_tag_alias = $wpdb->prefix . 'ps_tag_alias'; + + $sql = " + CREATE TABLE $table_name_classification ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + secret_id bigint(20) unsigned NOT NULL, + text longtext NOT NULL, + descriptors text, + confidences text, + PRIMARY KEY (id), + KEY secret_id (secret_id) + ) $charset_collate; + + CREATE TABLE $table_name_audit ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + actor_id bigint(20) unsigned NOT NULL, + action varchar(191) NOT NULL, + context longtext, + timestamp datetime NOT NULL DEFAULT CURRENT_TIMESTAMP, + PRIMARY KEY (id), + KEY actor_id (actor_id) + ) $charset_collate; + + CREATE TABLE $table_name_tag_alias ( + id bigint(20) unsigned NOT NULL AUTO_INCREMENT, + alias varchar(191) NOT NULL, + canonical varchar(191) NOT NULL, + PRIMARY KEY (id), + UNIQUE KEY alias (alias) + ) $charset_collate; + "; + + require_once ABSPATH . 'wp-admin/includes/upgrade.php'; + dbDelta( $sql ); +} diff --git a/wp-content/plugins/postsecret-admin/postsecret-admin.php b/wp-content/plugins/postsecret-admin/postsecret-admin.php new file mode 100644 index 0000000..484aa5e --- /dev/null +++ b/wp-content/plugins/postsecret-admin/postsecret-admin.php @@ -0,0 +1,48 @@ +id = $id; + $this->title = $title; + $this->content = $content; + $this->tags = $tags; + } +} diff --git a/wp-content/plugins/postsecret-admin/src/Routes/BackfillRoute.php b/wp-content/plugins/postsecret-admin/src/Routes/BackfillRoute.php new file mode 100644 index 0000000..0f20204 --- /dev/null +++ b/wp-content/plugins/postsecret-admin/src/Routes/BackfillRoute.php @@ -0,0 +1,37 @@ + 'POST', + 'callback' => [ $this, 'handle_backfill' ], + 'permission_callback' => function () { + return current_user_can( 'manage_options' ); + }, + ] + ); + } + + public function handle_backfill( \WP_REST_Request $request ) { + // TODO: Implement backfill via WP-CLI commands or BackfillService. + return rest_ensure_response( + [ + 'status' => 'queued', + ] + ); + } +} diff --git a/wp-content/plugins/postsecret-admin/src/Routes/ReviewRoute.php b/wp-content/plugins/postsecret-admin/src/Routes/ReviewRoute.php new file mode 100644 index 0000000..f97face --- /dev/null +++ b/wp-content/plugins/postsecret-admin/src/Routes/ReviewRoute.php @@ -0,0 +1,37 @@ + 'POST', + 'callback' => [ $this, 'handle_review' ], + 'permission_callback' => function () { + return current_user_can( 'edit_posts' ); + }, + ] + ); + } + + public function handle_review( \WP_REST_Request $request ) { + // TODO: Implement review logic via ModerationService. + return rest_ensure_response( + [ + 'status' => 'success', + ] + ); + } +} diff --git a/wp-content/plugins/postsecret-admin/src/Routes/SearchRoute.php b/wp-content/plugins/postsecret-admin/src/Routes/SearchRoute.php new file mode 100644 index 0000000..360c9d1 --- /dev/null +++ b/wp-content/plugins/postsecret-admin/src/Routes/SearchRoute.php @@ -0,0 +1,70 @@ + 'GET', + 'callback' => [ $this, 'handle_search' ], + 'permission_callback' => function () { + return current_user_can( 'edit_posts' ); + }, + 'args' => [ + 'q' => [ + 'description' => __( 'Search query', 'postsecret-admin' ), + 'type' => 'string', + ], + 'tags' => [ + 'description' => __( 'Tag filters (comma-separated)', 'postsecret-admin' ), + 'type' => 'string', + ], + 'page' => [ + 'description' => __( 'Page number', 'postsecret-admin' ), + 'type' => 'integer', + ], + ], + ] + ); + } + + /** + * Handle search requests. + * + * @param \WP_REST_Request $request Request object. + * @return \WP_REST_Response + */ + public function handle_search( \WP_REST_Request $request ) { + // TODO: Implement search logic via SearchService. + $query = sanitize_text_field( $request->get_param( 'q' ) ); + $tags = array_filter( array_map( 'trim', explode( ',', $request->get_param( 'tags' ) ) ) ); + $page = intval( $request->get_param( 'page' ) ); + + return rest_ensure_response( + [ + 'results' => [], + 'query' => $query, + 'tags' => $tags, + 'page' => $page, + ] + ); + } +} diff --git a/wp-content/plugins/postsecret-admin/src/Routes/SettingsRoute.php b/wp-content/plugins/postsecret-admin/src/Routes/SettingsRoute.php new file mode 100644 index 0000000..2630b11 --- /dev/null +++ b/wp-content/plugins/postsecret-admin/src/Routes/SettingsRoute.php @@ -0,0 +1,37 @@ + 'GET', + 'callback' => [ $this, 'get_settings' ], + 'permission_callback' => function () { + return current_user_can( 'manage_options' ); + }, + ] + ); + } + + public function get_settings() { + // TODO: Retrieve settings via ConfigService. + return rest_ensure_response( + [ + 'settings' => [], + ] + ); + } +} diff --git a/wp-content/plugins/postsecret-admin/src/Routes/TaxonomyRoute.php b/wp-content/plugins/postsecret-admin/src/Routes/TaxonomyRoute.php new file mode 100644 index 0000000..48df014 --- /dev/null +++ b/wp-content/plugins/postsecret-admin/src/Routes/TaxonomyRoute.php @@ -0,0 +1,37 @@ + 'POST', + 'callback' => [ $this, 'handle_taxonomy' ], + 'permission_callback' => function () { + return current_user_can( 'manage_categories' ); + }, + ] + ); + } + + public function handle_taxonomy( \WP_REST_Request $request ) { + // TODO: Implement taxonomy management via TaxonomyService. + return rest_ensure_response( + [ + 'status' => 'success', + ] + ); + } +} diff --git a/wp-content/plugins/postsecret-admin/src/Services/ConfigService.php b/wp-content/plugins/postsecret-admin/src/Services/ConfigService.php new file mode 100644 index 0000000..f49439a --- /dev/null +++ b/wp-content/plugins/postsecret-admin/src/Services/ConfigService.php @@ -0,0 +1,32 @@ +assertTrue( true ); + } +} diff --git a/wp-content/plugins/postsecret-admin/vendor/autoload.php b/wp-content/plugins/postsecret-admin/vendor/autoload.php new file mode 100644 index 0000000..36519d7 --- /dev/null +++ b/wp-content/plugins/postsecret-admin/vendor/autoload.php @@ -0,0 +1,33 @@ +setPsr4('PostSecret\\Admin\\', array(__DIR__ . '/../../src')); + $loader->register(true); + + return $loader; + } + + public static function loadClassLoader($class) + { + if ('Composer\Autoload\ClassLoader' === $class) { + require __DIR__ . '/ClassLoader.php'; + } + } +} diff --git a/wp-content/plugins/postsecret-ai/postsecret-ai.php b/wp-content/plugins/postsecret-ai/postsecret-ai.php new file mode 100644 index 0000000..015690a --- /dev/null +++ b/wp-content/plugins/postsecret-ai/postsecret-ai.php @@ -0,0 +1,287 @@ +

Postcards

Choose a submenu: Upload Single.

'; + }, + 'dashicons-format-image', + 25 + ); + + add_submenu_page( + 'psai_postcards', + 'Upload Single', + 'Upload Single', + 'upload_files', + 'psai_upload_single', + ['PSAI\\AdminSingleUpload', 'render'] + ); +}); + +/* Settings (tester page) */ +add_action('admin_init', ['PSAI\\Settings', 'register']); + +/* --------------------------------------------------------------------------- + * Tester handler (Tools page) — URL-based single image test + * ------------------------------------------------------------------------- */ +add_action('admin_post_psai_classify', function () { + if (!current_user_can('manage_options')) wp_die('Unauthorized', 403); + check_admin_referer('psai_classify'); + + $env = get_option(\PSAI\Settings::OPTION, \PSAI\Settings::defaults()); + $api = $env['API_KEY'] ?? ''; + $model = $env['MODEL_NAME'] ?? 'gpt-4o-mini'; + $image = isset($_POST['psai_image_url']) ? esc_url_raw(trim($_POST['psai_image_url'])) : ''; + + if (!$api || !$image) { + $q = ['page' => PSAI_SLUG, 'psai_err' => !$api ? 'no_key' : 'no_image']; + wp_redirect(add_query_arg($q, admin_url('tools.php'))); + exit; + } + + try { + $payload = \PSAI\Classifier::classify($api, $model, $image, null); + set_transient('psai_last_result', [ + 'image_url' => $image, + 'model' => $model, + 'payload' => $payload, + 'ts' => time(), + ], 600); + + wp_redirect(add_query_arg(['page' => PSAI_SLUG, 'psai_done' => '1'], admin_url('tools.php'))); + exit; + } catch (\Throwable $e) { + set_transient('psai_last_error', $e->getMessage(), 300); + wp_redirect(add_query_arg(['page' => PSAI_SLUG, 'psai_err' => 'call_failed'], admin_url('tools.php'))); + exit; + } +}); + +/* --------------------------------------------------------------------------- + * Admin: Upload Single (front required, back optional) + * - sideloads to Media + * - indexes + pairs + * - queues & triggers classification + * ------------------------------------------------------------------------- */ +add_action('admin_post_psai_upload_single', function () { + if (!current_user_can('upload_files')) wp_die('Not allowed', 403); + check_admin_referer('psai_upload_single'); + + $front_id = null; + $back_id = null; + $had_dupe = false; + + // 1) FRONT (required) + if (empty($_FILES['psai_front']['name'])) { + wp_redirect(add_query_arg(['page' => 'psai_upload_single', 'psai_msg' => 'err'], admin_url('admin.php'))); + exit; + } + $front_id = \PSAI\Ingress::sideload($_FILES['psai_front'], 'front'); + if (!$front_id) { + wp_redirect(add_query_arg(['page' => 'psai_upload_single', 'psai_msg' => 'err'], admin_url('admin.php'))); + exit; + } + if (\PSAI\Ingress::mark_exact_duplicate($front_id)) $had_dupe = true; + + // 2) BACK (optional) + if (!empty($_FILES['psai_back']['name'])) { + $back_id = \PSAI\Ingress::sideload($_FILES['psai_back'], 'back'); + if ($back_id) { + \PSAI\Ingress::pair($front_id, $back_id); + if (\PSAI\Ingress::mark_exact_duplicate($back_id)) $had_dupe = true; + } + } + + // 3) Queue pair processing (and also run immediately once) + if (!get_post_meta($front_id, '_ps_duplicate_of', true)) { + wp_schedule_single_event(time() + 5, 'psai_process_pair_event', [$front_id, (int)($back_id ?? 0)]); + do_action('psai_process_pair_event', $front_id, (int)($back_id ?? 0)); + } + + $msg = $had_dupe ? 'dupe' : 'ok'; + wp_redirect(add_query_arg(['page' => 'psai_upload_single', 'psai_msg' => $msg], admin_url('admin.php'))); + exit; +}); + +/* --------------------------------------------------------------------------- + * Background (and on-demand) processor for a front/back pair + * - builds data URLs (works on localhost/private) + * - classifies + * - stores payload + flags, syncs attachment fields + * - computes orientation/color + * ------------------------------------------------------------------------- */ +add_action('psai_process_pair_event', function ($front_id, $back_id = 0) { + $front_id = (int)$front_id; + $back_id = (int)$back_id ?: null; + + $env = get_option(\PSAI\Settings::OPTION, \PSAI\Settings::defaults()); + $api = $env['API_KEY'] ?? ''; + $model = $env['MODEL_NAME'] ?? 'gpt-4o-mini'; + + if (!$api || !$front_id) return; + if (get_post_meta($front_id, '_ps_duplicate_of', true)) return; + + try { + // Data URLs so we don’t rely on public URLs + $frontSrc = \PSAI\psai_make_data_url($front_id); + $backSrc = $back_id ? \PSAI\psai_make_data_url($back_id) : null; + + // Classify + normalize to schema + $payload = \PSAI\Classifier::classify($api, $model, $frontSrc, $backSrc); + $payload = \PSAI\SchemaGuard::normalize($payload); + + // Store result (sets tags, model, prompt version, vetted flags) + \PSAI\psai_store_result($front_id, $payload, $model); + + // Sync media fields (Alt/Caption/Description) — safe no-op if class missing + \PSAI\AttachmentSync::sync_from_payload($front_id, $payload, $back_id); + + // Enrich quick orientation/color on both sides + \PSAI\Metadata::compute_and_store($front_id); + if ($back_id) \PSAI\Metadata::compute_and_store($back_id); + + // Optional export manifest + \PSAI\psai_update_manifest($front_id, $payload); + + // Mirror some fields onto back (paired) for convenience + if ($back_id) { + update_post_meta($back_id, '_ps_pair_id', $front_id); + update_post_meta($back_id, '_ps_side', 'back'); + update_post_meta($back_id, '_ps_payload', $payload); + update_post_meta($back_id, '_ps_tags', get_post_meta($front_id, '_ps_tags', true)); + update_post_meta($back_id, '_ps_model', get_post_meta($front_id, '_ps_model', true)); + update_post_meta($back_id, '_ps_prompt_version', get_post_meta($front_id, '_ps_prompt_version', true)); + update_post_meta($back_id, '_ps_updated_at', wp_date('c')); + + // keep vetted flags mirrored on back for UI/API convenience + $rs = get_post_meta($front_id, '_ps_review_status', true); + update_post_meta($back_id, '_ps_review_status', $rs); + update_post_meta($back_id, '_ps_is_vetted', $rs === 'auto_vetted' ? '1' : '0'); + } + + delete_post_meta($front_id, '_ps_last_error'); + if ($back_id) delete_post_meta($back_id, '_ps_last_error'); + + } catch (\Throwable $e) { + $msg = substr($e->getMessage(), 0, 500); + update_post_meta($front_id, '_ps_last_error', $msg); + if ($back_id) update_post_meta($back_id, '_ps_last_error', $msg); + } +}, 10, 2); + +/* --------------------------------------------------------------------------- + * “Process now” button on the attachment edit screen + * - If payload missing → classify this single attachment (front-only) + * - In all cases → normalize flags + compute orientation/color + * ------------------------------------------------------------------------- */ +add_action('admin_post_psai_process_now', function () { + if (!current_user_can('upload_files')) wp_die('Not allowed', 403); + + $att = isset($_GET['att']) ? (int)$_GET['att'] : 0; + check_admin_referer('psai_process_now_' . $att); + if (!$att || get_post_type($att) !== 'attachment') { + wp_redirect(admin_url('upload.php?psai_msg=bad_id')); + exit; + } + + try { + // If this is the back, flip to the front as canonical + $maybePair = (int)get_post_meta($att, '_ps_pair_id', true); + $side = get_post_meta($att, '_ps_side', true); + $front_id = ($side === 'back' && $maybePair) ? $maybePair : $att; + + // Classify if we don't already have a payload + $payload = get_post_meta($front_id, '_ps_payload', true); + if (!is_array($payload)) { + $env = get_option(\PSAI\Settings::OPTION, \PSAI\Settings::defaults()); + $api = $env['API_KEY'] ?? ''; + $model = $env['MODEL_NAME'] ?? 'gpt-4o-mini'; + if (!$api) throw new \RuntimeException('Missing OpenAI API key.'); + + $frontSrc = \PSAI\psai_make_data_url($front_id); + $payload = \PSAI\Classifier::classify($api, $model, $frontSrc, null); + $payload = \PSAI\SchemaGuard::normalize($payload); + + \PSAI\psai_store_result($front_id, $payload, $model); + \PSAI\AttachmentSync::sync_from_payload($front_id, $payload, null); + } + + // Normalize flags from the saved payload + recompute metadata + \PSAI\Ingress::normalize_from_existing_payload($front_id); + + // Also compute orientation/color for the side the user is viewing + \PSAI\Metadata::compute_and_store($att); + + delete_post_meta($front_id, '_ps_last_error'); + + $url = add_query_arg(['psai_msg' => 'ok'], get_edit_post_link($att, '')); + wp_safe_redirect($url ?: admin_url('upload.php?psai_msg=ok')); + exit; + + } catch (\Throwable $e) { + update_post_meta($att, '_ps_last_error', substr($e->getMessage(), 0, 500)); + $url = add_query_arg(['psai_msg' => 'err'], get_edit_post_link($att, '')); + wp_safe_redirect($url ?: admin_url('upload.php?psai_msg=err')); + exit; + } +}); + +/* Small admin notice so you know the button worked */ +add_action('admin_notices', function () { + if (!is_admin() || !isset($_GET['psai_msg'])) return; + $msg = sanitize_text_field($_GET['psai_msg']); + if ($msg === 'ok') { + echo '

PostSecret AI: Attachment normalized.

'; + } elseif ($msg === 'err') { + echo '

PostSecret AI: There was an error. See the meta box for details.

'; + } elseif ($msg === 'bad_id') { + echo '

PostSecret AI: Invalid attachment.

'; + } elseif ($msg === 'dupe') { + echo '

PostSecret AI: Uploaded image matches an existing file (duplicate marked).

'; + } +}); \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/AdminMetaBox.php b/wp-content/plugins/postsecret-ai/src/AdminMetaBox.php new file mode 100644 index 0000000..f6305a0 --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/AdminMetaBox.php @@ -0,0 +1,172 @@ +ID, '_ps_tags', true) ?: []; + $model = get_post_meta($post->ID, '_ps_model', true); + $pver = get_post_meta($post->ID, '_ps_prompt_version', true); + $when = get_post_meta($post->ID, '_ps_updated_at', true); + $payload = get_post_meta($post->ID, '_ps_payload', true); + + // Health / linkage + $err = get_post_meta($post->ID, '_ps_last_error', true); + $dup = get_post_meta($post->ID, '_ps_duplicate_of', true); + $near = get_post_meta($post->ID, '_ps_near_duplicate_of', true); + $pair = get_post_meta($post->ID, '_ps_pair_id', true); + + // Triage flags + $side = get_post_meta($post->ID, '_ps_side', true) ?: '—'; + $review = get_post_meta($post->ID, '_ps_review_status', true) ?: '—'; + $vetted = get_post_meta($post->ID, '_ps_is_vetted', true); + $vetted = ($vetted === '1' || $vetted === 1 || $vetted === true) ? 'yes' : 'no'; + + // Presentation status + $status = 'Queued'; + if ($err) $status = 'Error'; + elseif ($dup) $status = 'Duplicate'; + elseif ($near) $status = 'Near-duplicate'; + elseif ($payload) $status = 'Classified'; + + // Action: Process now (normalizes + classifies if needed) + $proc_url = wp_nonce_url( + admin_url('admin-post.php?action=psai_process_now&att=' . (int)$post->ID), + 'psai_process_now_' . (int)$post->ID + ); + echo '

Process now

'; + + echo '
'; + + // Header status + echo '

Status: ' . esc_html($status) . '

'; + + // Triage summary (side / review / vetted) + echo '

Side: ' . esc_html($side) . '
'; + echo 'Review: ' . esc_html($review) . '
'; + echo 'Vetted: ' . esc_html($vetted) . '

'; + + // Tags + if ($tags && is_array($tags)) { + echo '

Tags:
'; + foreach ($tags as $t) echo '' . esc_html($t) . ' '; + echo '

'; + } + + // Model / prompt / timestamp + echo '

Model: ' . esc_html($model ?: '—') . '
'; + echo 'Prompt: ' . esc_html($pver ?: '—') . '
'; + echo 'Updated: ' . esc_html($when ?: '—') . '

'; + + // Quick visual metadata + $orient = get_post_meta($post->ID, '_ps_orientation', true); + $primary = get_post_meta($post->ID, '_ps_primary_hex', true); + $palette = get_post_meta($post->ID, '_ps_palette', true) ?: []; + + echo '

Orientation: ' . esc_html($orient ?: '—') . '

'; + if ($primary) { + echo '

Primary: ' . esc_html($primary) . '

'; + } + if ($palette) { + echo '

Palette:
'; + foreach ((array)$palette as $hex) { + $hex = (string)$hex; + echo '' . esc_html($hex) . ' '; + } + echo '

'; + } + + // Pair / duplicates + if ($pair) { + $url = get_edit_post_link((int)$pair); + echo '

Paired side: View

'; + } + if ($dup) { + $url = get_edit_post_link((int)$dup); + echo '

Duplicate of: View original

'; + } elseif ($near) { + $url = get_edit_post_link((int)$near); + echo '

Near-duplicate of: View candidate

'; + } + + // Error (if any) + if ($err) { + echo '

Error:
' . esc_html($err) . '

'; + } + + // Raw JSON viewer + if ($payload) { + $json = wp_json_encode($payload, JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES); + echo '
View raw JSON'; + echo ''; + echo '
'; + } + + echo '
'; + } + + public static function colAdd($cols) + { + $cols['psai'] = 'PS'; + return $cols; + } + + public static function colRender($col, $attach_id) + { + if ($col !== 'psai') return; + + $err = get_post_meta($attach_id, '_ps_last_error', true); + $dup = get_post_meta($attach_id, '_ps_duplicate_of', true); + $near = get_post_meta($attach_id, '_ps_near_duplicate_of', true); + $has = get_post_meta($attach_id, '_ps_payload', true); + + if ($err) echo '!'; + elseif ($dup) echo '='; + elseif ($near) echo ''; + elseif ($has) echo ''; + else echo ''; + } + + public static function assets($hook) + { + if ($hook !== 'upload.php' && $hook !== 'post.php') return; + + $css = ' + .psai-badge{display:inline-block;padding:2px 8px;border-radius:999px;background:#e9eff5} + .psai-chip{display:inline-block;margin:2px 4px 0 0;padding:2px 8px;border-radius:12px;background:#f0f2f4;font-size:12px} + .psai-dot{display:inline-block;font-weight:700} + .psai-green{color:#008a20}.psai-blue{color:#2271b1}.psai-gray{color:#777}.psai-amber{color:#b95000}.psai-red{color:#b32d2e} + .psai-box details{margin-top:6px} + .psai-box textarea{margin-top:8px} + + /* review status pill colors */ + .psai-pill{display:inline-block;padding:2px 8px;border-radius:999px;background:#eef3f8;font-size:12px} + .psai-rv-auto_vetted{background:#e6f8ec;color:#165f2d} + .psai-rv-needs_review{background:#fff3e6;color:#7a3e00} + .psai-rv-reject_candidate{background:#fdeaea;color:#7d1c1c} + '; + + // Use a core style handle so inline CSS prints in admin + wp_add_inline_style('common', $css); + } +} + +AdminMetaBox::boot(); \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/AdminPage.php b/wp-content/plugins/postsecret-ai/src/AdminPage.php new file mode 100644 index 0000000..eadfd96 --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/AdminPage.php @@ -0,0 +1,81 @@ +

PostSecret AI

'; + + if (isset($_GET['settings-updated'])) { + echo '

Settings saved.

'; + } + settings_errors('postsecret-ai'); // shows any add_settings_error() messages + + // Notices + if ($err === 'no_key') { + echo '

Add your OpenAI API key in Settings below.

'; + } elseif ($err === 'no_image') { + echo '

Provide an image URL.

'; + } elseif ($err === 'call_failed') { + $msg = get_transient('psai_last_error'); + if ($msg) echo '

Classifier error: ' . esc_html($msg) . '

'; + } elseif ($done) { + echo '

Classification complete.

'; + } + + // Classify form + ?> +

Classify a Single Image

+
+ + + + + + + + + +
+

Last Result

'; + echo '

Image: ' . esc_html($last['image_url']) . '

'; + echo '

Model: ' . esc_html($last['model']) . '

'; + if (!empty($last['payload']['approvedText'])) { + echo '

Approved Text:
' . esc_html($last['payload']['approvedText']) . '

'; + } + if (!empty($last['payload']['tags'])) { + echo '

Tags: ' . esc_html(implode(', ', $last['payload']['tags'])) . '

'; + } + echo '
Raw JSON payload
'
+                    . esc_html(json_encode($last['payload'], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES))
+                    . '
'; + } + + // Settings stub + echo '

Settings

'; + echo '
'; + settings_fields(PSAI_SLUG); + do_settings_sections(PSAI_SLUG); + submit_button('Save Settings'); + echo '
'; + + echo ''; + } +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/AdminSingleUpload.php b/wp-content/plugins/postsecret-ai/src/AdminSingleUpload.php new file mode 100644 index 0000000..33278b1 --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/AdminSingleUpload.php @@ -0,0 +1,260 @@ +

Upload a Postcard

'; + echo '

Front image is required. Back is optional. If provided, both sides will be classified together in one request.

'; + + if ($notice === 'ok') { + echo '

Uploaded. Processing queued.

'; + } elseif ($notice === 'dupe') { + echo '

Upload complete. One or more files matched existing images (duplicates were not re-classified).

'; + } elseif ($notice === 'err') { + echo '

Upload failed. Check file types/size and try again.

'; + } + + $action = esc_url(admin_url('admin-post.php')); + $nonce = wp_create_nonce('psai_upload_single'); + $accept = 'image/jpeg,image/png,image/webp,image/tiff'; + $maxMb = 25; // client-side soft cap; server may allow more + ?> + + +
+
+ + + +
+ +
+ Front (required) + +
+ Drop image here or choose a file +
+
+
+ + +
+ Back (optional) + +
+ Drop image here or choose a file +
+
+
+
+ +

Allowed: JPG, PNG, WEBP, TIFF. Max ~ MB per file. Backs are always classified when provided.

+ +
+ + Both sides will be sent together in one classification request. +
+
+
+ + + '; + } +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/AttachmentSync.php b/wp-content/plugins/postsecret-ai/src/AttachmentSync.php new file mode 100644 index 0000000..8c46aba --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/AttachmentSync.php @@ -0,0 +1,101 @@ + from front/back artDescription (fallback: secretDescription) + * - Caption -> from tags (e.g., "#addiction #remorseful"), max 140 chars + * - Description -> from secretDescription (objective summary) + * + * Rules: + * - Only fill when the field is empty (don’t overwrite manual edits). + * - Never include long transcriptions or anything when containsPII=true. + * - Back attachment gets “Back of postcard …” phrasing. + */ +final class AttachmentSync +{ + public static function sync_from_payload(int $front_id, array $payload, ?int $back_id = null): void + { + $containsPII = (bool)($payload['moderation']['containsPII'] ?? false); + $tags = is_array($payload['tags'] ?? null) ? $payload['tags'] : []; + $secretDesc = self::clean_str($payload['secretDescription'] ?? ''); + + // FRONT + $frontSide = $payload['front'] ?? []; + $frontArt = self::clean_str($frontSide['artDescription'] ?? ''); + $frontAlt = $frontArt ?: $secretDesc; + $frontCaption = self::format_caption($tags); + $frontDesc = $secretDesc; + + self::apply_if_empty($front_id, $frontAlt, $frontCaption, $frontDesc, 'front', $containsPII); + + // BACK (optional) + if ($back_id) { + $backSide = $payload['back'] ?? []; + $backArt = self::clean_str($backSide['artDescription'] ?? ''); + $altBack = $backArt ?: 'Back of postcard'; + $capBack = $frontCaption; // keep tags consistent + $descBack = $containsPII ? '' : self::clean_str($backArt); // stay minimal on back + + self::apply_if_empty($back_id, $altBack, $capBack, $descBack, 'back', $containsPII); + } + } + + private static function apply_if_empty(int $att_id, string $alt, string $caption, string $desc, string $side, bool $containsPII): void + { + // ALT (short, no HTML) + $alt = self::clip_words($alt ?: ($side === 'back' ? 'Back of postcard' : 'Postcard front'), 120); + $existingAlt = get_post_meta($att_id, '_wp_attachment_image_alt', true); + if ($existingAlt === '' && $alt !== '') { + update_post_meta($att_id, '_wp_attachment_image_alt', $alt); + } + + // CAPTION (tags → “#tag #tag …” up to 140 chars) + $existingPost = get_post($att_id); + $existingCap = is_object($existingPost) ? trim((string)$existingPost->post_excerpt) : ''; + if ($existingCap === '' && $caption !== '') { + // Guard against PII: captions won’t include transcription anyway, so safe + wp_update_post(['ID' => $att_id, 'post_excerpt' => $caption]); + } + + // DESCRIPTION (only objective summary; skip if PII) + $existingDesc = is_object($existingPost) ? trim((string)$existingPost->post_content) : ''; + if ($existingDesc === '' && !$containsPII && $desc !== '') { + // Preserve line breaks; strip dangerous tags + $safe = esc_html($desc); + $safe = str_replace("\n", "\n\n", $safe); // WP autop likes blank lines + wp_update_post(['ID' => $att_id, 'post_content' => $safe]); + } + } + + private static function format_caption(array $tags): string + { + if (empty($tags)) return ''; + // 3–5 tags is plenty for a caption + $tags = array_slice($tags, 0, 5); + $hashes = array_map(fn($t) => '#' . preg_replace('/[^a-z0-9_]/', '', strtolower((string)$t)), $tags); + $cap = implode(' ', $hashes); + // keep it terse + if (mb_strlen($cap, 'UTF-8') > 140) { + $cap = mb_substr($cap, 0, 137, 'UTF-8') . '…'; + } + return $cap; + } + + private static function clean_str($s): string + { + if (!is_string($s)) return ''; + $s = trim(preg_replace('/\s+/u', ' ', $s)); + return $s; + } + + /** Clip string roughly by character count, not cutting mid-grapheme. */ + private static function clip_words(string $s, int $limit): string + { + if (mb_strlen($s, 'UTF-8') <= $limit) return $s; + return rtrim(mb_substr($s, 0, $limit - 1, 'UTF-8')) . '…'; + } +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/Classifier.php b/wp-content/plugins/postsecret-ai/src/Classifier.php new file mode 100644 index 0000000..8127bb7 --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/Classifier.php @@ -0,0 +1,58 @@ + 'text', 'text' => 'SIDE: front'], + ['type' => 'image_url', 'image_url' => ['url' => $frontUrl, 'detail' => 'high']], + ]; + if ($backUrl) { + $userContent[] = ['type' => 'text', 'text' => 'SIDE: back']; + $userContent[] = ['type' => 'image_url', 'image_url' => ['url' => $backUrl, 'detail' => 'high']]; + } + + $messages = [ + ['role' => 'system', 'content' => Prompt::TEXT], + ['role' => 'user', 'content' => $userContent], + ]; + + $body = [ + 'model' => $model, + 'temperature' => 0.2, + 'response_format' => ['type' => 'json_object'], + 'messages' => $messages, + ]; + + $res = wp_remote_post($endpoint, [ + 'headers' => [ + 'Authorization' => 'Bearer ' . $apiKey, + 'Content-Type' => 'application/json', + ], + 'timeout' => (int)(get_option('psai_env')['REQUEST_TIMEOUT_SECONDS'] ?? 60), + 'body' => wp_json_encode($body), + ]); + + if (is_wp_error($res)) throw new \RuntimeException($res->get_error_message()); + $code = wp_remote_retrieve_response_code($res); + $raw = wp_remote_retrieve_body($res); + if ($code >= 300) throw new \RuntimeException('OpenAI HTTP ' . $code . ': ' . substr($raw, 0, 500)); + + $json = json_decode($raw, true); + $content = $json['choices'][0]['message']['content'] ?? ''; + $payload = json_decode($content, true); + if (!is_array($payload)) throw new \RuntimeException('Unexpected model response.'); + + $payload = \PSAI\SchemaGuard::normalize($payload); + + return $payload; + } +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/Ingress.php b/wp-content/plugins/postsecret-ai/src/Ingress.php new file mode 100644 index 0000000..4911a46 --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/Ingress.php @@ -0,0 +1,255 @@ + sanitize_file_name($fileArr['name'] ?? ''), + 'type' => (string)($fileArr['type'] ?? ''), + 'tmp_name' => (string)($fileArr['tmp_name'] ?? ''), + 'error' => (int)($fileArr['error'] ?? 0), + 'size' => (int)($fileArr['size'] ?? 0), + ]; + if (empty($fa['tmp_name']) || !is_uploaded_file($fa['tmp_name'])) { + return null; + } + + $att_id = media_handle_sideload($fa, 0); + if (is_wp_error($att_id)) { + return null; + } + $att_id = (int)$att_id; + + // Side + initial flags + update_post_meta($att_id, '_ps_side', ($side === 'back') ? 'back' : 'front'); + update_post_meta($att_id, '_ps_is_vetted', '0'); + update_post_meta($att_id, '_ps_review_status', 'needs_review'); // neutral default + delete_post_meta($att_id, '_ps_last_error'); + delete_post_meta($att_id, '_ps_duplicate_of'); + delete_post_meta($att_id, '_ps_near_duplicate_of'); + + // Basic index (hash/dims/bytes) + self::index($att_id); + + // Optional enrichments: orientation + color/palette + if (class_exists('\PSAI\Metadata') && method_exists('\PSAI\Metadata', 'compute_and_store')) { + \PSAI\Metadata::compute_and_store($att_id); + } + + // If you keep caption/alt/description in sync with AI later, + // they'll be filled by AttachmentSync after classification. + + return $att_id; + } + + /** + * Index the underlying file for quick duplicate/size checks. + * Stores: _ps_sha256, _ps_w, _ps_h, _ps_size + */ + public static function index(int $att_id): void + { + $path = get_attached_file($att_id); + if (!$path || !file_exists($path)) return; + + $sha = @hash_file('sha256', $path) ?: ''; + update_post_meta($att_id, '_ps_sha256', $sha); + + $w = 0; + $h = 0; + $info = @getimagesize($path); + if (is_array($info)) { + $w = (int)($info[0] ?? 0); + $h = (int)($info[1] ?? 0); + } + update_post_meta($att_id, '_ps_w', $w); + update_post_meta($att_id, '_ps_h', $h); + update_post_meta($att_id, '_ps_size', (int)(@filesize($path) ?: 0)); + } + + /** + * If another attachment already has the same _ps_sha256, + * label current as exact duplicate. + * + * @return bool true if a duplicate was found and marked. + */ + public static function mark_exact_duplicate(int $att_id): bool + { + $sha = get_post_meta($att_id, '_ps_sha256', true); + if (!$sha) return false; + + $q = new \WP_Query([ + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'fields' => 'ids', + 'posts_per_page' => 1, + 'meta_query' => [ + ['key' => '_ps_sha256', 'value' => $sha], + ], + 'post__not_in' => [$att_id], + ]); + + if ($q->have_posts()) { + update_post_meta($att_id, '_ps_duplicate_of', (int)$q->posts[0]); + return true; + } + return false; + } + + /** + * Pair two attachments together (both directions) and ensure side labels. + */ + public static function pair(?int $front_id, ?int $back_id): void + { + if ($front_id && $back_id) { + update_post_meta($front_id, '_ps_pair_id', $back_id); + update_post_meta($back_id, '_ps_pair_id', $front_id); + update_post_meta($front_id, '_ps_side', 'front'); + update_post_meta($back_id, '_ps_side', 'back'); + } + } + + /** + * Normalize flags from a previously saved AI payload. + * Useful for “Process now” or any repair action. + */ + public static function normalize_from_existing_payload(int $att_id): void + { + $payload = get_post_meta($att_id, '_ps_payload', true); + if (!is_array($payload)) return; + + $side = get_post_meta($att_id, '_ps_side', true); + if ($side !== 'front' && $side !== 'back') { + update_post_meta($att_id, '_ps_side', 'front'); + } + + $review = $payload['moderation']['reviewStatus'] ?? 'auto_vetted'; + update_post_meta($att_id, '_ps_review_status', $review); + update_post_meta($att_id, '_ps_is_vetted', ($review === 'auto_vetted') ? '1' : '0'); + + // Recompute orientation/color if helper exists + if (class_exists('\PSAI\Metadata') && method_exists('\PSAI\Metadata', 'compute_and_store')) { + \PSAI\Metadata::compute_and_store($att_id); + } + } +} + +/** + * Store normalized payload + versioning on the canonical (front) attachment. + * Also sets cheap filter fields for REST/meta_query. + */ +function psai_store_result(int $att_id, array $payload, string $model): void +{ + $promptVer = \PSAI\Prompt::VERSION . '#sha256:' . substr(hash('sha256', \PSAI\Prompt::TEXT), 0, 8); + $tags = array_values(array_filter(array_map('strval', $payload['tags'] ?? []))); + sort($tags); + + update_post_meta($att_id, '_ps_payload', $payload); + update_post_meta($att_id, '_ps_tags', $tags); + update_post_meta($att_id, '_ps_model', $model); + update_post_meta($att_id, '_ps_prompt_version', $promptVer); + update_post_meta($att_id, '_ps_updated_at', wp_date('c')); + + // Vetted flags (store as strings for WP meta_query) + $review = $payload['moderation']['reviewStatus'] ?? 'auto_vetted'; + update_post_meta($att_id, '_ps_review_status', $review); // auto_vetted | needs_review | reject_candidate + update_post_meta($att_id, '_ps_is_vetted', ($review === 'auto_vetted') ? '1' : '0'); + + // Optional: sync attachment Alt/Caption/Description from payload (if you use it) + if (class_exists('\PSAI\AttachmentSync')) { + \PSAI\AttachmentSync::sync_from_payload($att_id, $payload, null); + } +} + +/** + * (Optional) Add/refresh a simple manifest file for exports. + */ +function psai_update_manifest(int $att_id, array $payload): void +{ + $u = wp_upload_dir(); + $dir = trailingslashit($u['basedir']) . 'postsecret-ai'; + wp_mkdir_p($dir); + $path = $dir . '/manifest.json'; + + $existing = file_exists($path) ? (json_decode(file_get_contents($path), true) ?: []) : []; + $items = $existing['items'] ?? []; + + $file = wp_basename(get_attached_file($att_id)); + $entry = ['sourceImage' => $file, 'json' => $att_id . '.json']; + if (!empty($payload['tags'])) $entry['tags'] = array_values((array)$payload['tags']); + + // upsert by sourceImage + $by = []; + foreach ($items as $it) { + if (!empty($it['sourceImage'])) $by[$it['sourceImage']] = $it; + } + $by[$file] = $entry; + + file_put_contents($path, json_encode(['items' => array_values($by)], JSON_PRETTY_PRINT | JSON_UNESCAPED_SLASHES)); +} + +/** + * Convert an attachment to a JPEG data URL (scaled down for tokens/bandwidth). + * + * @return string data:image/jpeg;base64,... (throws on failure) + */ +function psai_make_data_url(int $att_id, int $maxDim = 1600, int $quality = 85): string +{ + $path = get_attached_file($att_id); + if (!$path || !file_exists($path)) { + throw new \RuntimeException('Attachment file not found.'); + } + + // Use WP image editor to normalize and resize + $editor = wp_get_image_editor($path); + if (is_wp_error($editor)) { + $raw = @file_get_contents($path); + if ($raw === false) throw new \RuntimeException('Failed to read image.'); + $mime = wp_check_filetype($path)['type'] ?: 'image/jpeg'; + return 'data:' . $mime . ';base64,' . base64_encode($raw); + } + + // Constrain for model-friendly size + $size = $editor->get_size(); + $w = (int)($size['width'] ?? 0); + $h = (int)($size['height'] ?? 0); + if ($w > $maxDim || $h > $maxDim) { + $editor->resize($maxDim, $maxDim, false); + } + $editor->set_quality($quality); + + // Save temp JPEG → base64 + $tmp = wp_tempnam('psai'); + $tmpJpg = $tmp . '.jpg'; + $saved = $editor->save($tmpJpg, 'image/jpeg'); + if (is_wp_error($saved) || empty($saved['path'])) { + $raw = @file_get_contents($path); + if ($raw === false) throw new \RuntimeException('Failed to read image.'); + $mime = wp_check_filetype($path)['type'] ?: 'image/jpeg'; + return 'data:' . $mime . ';base64,' . base64_encode($raw); + } + + $bytes = @file_get_contents($saved['path']); + @unlink($saved['path']); + if ($bytes === false) throw new \RuntimeException('Failed to read temp JPEG.'); + return 'data:image/jpeg;base64,' . base64_encode($bytes); +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/Metadata.php b/wp-content/plugins/postsecret-ai/src/Metadata.php new file mode 100644 index 0000000..2587862 --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/Metadata.php @@ -0,0 +1,134 @@ + $orientation, 'primary_hex' => $primary, 'palette' => $palette]; + } + + private static function orientation_from_size(int $w, int $h): string + { + if ($w <= 0 || $h <= 0) return 'unknown'; + if ($w === $h) return 'square'; + return ($w > $h) ? 'landscape' : 'portrait'; + } + + /** + * Returns [primary_hex, palette_hexes[]]. + * Tries Imagick (fast, accurate) then GD (portable). Falls back to white. + */ + private static function palette_hexes(string $path, int $k = 5): array + { + // Try Imagick histogram if available + if (class_exists('\Imagick')) { + try { + $im = new \Imagick($path); + // Downscale for speed + $im->thumbnailImage(512, 512, true); + $hist = $im->getImageHistogram(); // array of ImagickPixel + $counts = []; + foreach ($hist as $px) { + /** @var \ImagickPixel $px */ + $rgb = $px->getColor(); // ['r'=>..,'g'=>..,'b'=>..] + $hex = self::rgb_hex($rgb['r'], $rgb['g'], $rgb['b']); + $counts[$hex] = ($counts[$hex] ?? 0) + 1; + } + arsort($counts); + $hexes = array_slice(array_keys($counts), 0, max(1, $k)); + return [$hexes[0] ?? '#ffffff', $hexes]; + } catch (\Throwable $e) { /* fall through */ + } + } + + // GD fallback: load, shrink, quantize into 4-bit buckets, count + if (function_exists('imagecreatefromstring')) { + $bytes = @file_get_contents($path); + if ($bytes !== false) { + $src = @imagecreatefromstring($bytes); + if ($src !== false) { + $sw = imagesx($src); + $sh = imagesy($src); + $max = 256; + $scale = ($sw > $sh) ? ($max / max(1, $sw)) : ($max / max(1, $sh)); + $tw = max(1, (int)round($sw * $scale)); + $th = max(1, (int)round($sh * $scale)); + $tmp = imagecreatetruecolor($tw, $th); + imagecopyresampled($tmp, $src, 0, 0, 0, 0, $tw, $th, $sw, $sh); + + $counts = []; + // stride to keep it cheap + $sx = max(1, (int)floor($tw / 64)); + $sy = max(1, (int)floor($th / 64)); + for ($y = 0; $y < $th; $y += $sy) { + for ($x = 0; $x < $tw; $x += $sx) { + $idx = imagecolorat($tmp, $x, $y); + $r = ($idx >> 16) & 0xFF; + $g = ($idx >> 8) & 0xFF; + $b = $idx & 0xFF; + // 4-bit/channel quantization: 0..15 + $rq = $r >> 4; + $gq = $g >> 4; + $bq = $b >> 4; + // center back to 0..255 + $rc = ($rq << 4) | 0x8; + $gc = ($gq << 4) | 0x8; + $bc = ($bq << 4) | 0x8; + $hex = self::rgb_hex($rc, $gc, $bc); + $counts[$hex] = ($counts[$hex] ?? 0) + 1; + } + } + imagedestroy($tmp); + imagedestroy($src); + if ($counts) { + arsort($counts); + $hexes = array_slice(array_keys($counts), 0, max(1, $k)); + return [$hexes[0], $hexes]; + } + } + } + } + + // last resort + return ['#ffffff', ['#ffffff']]; + } + + private static function rgb_hex(int $r, int $g, int $b): string + { + $r = max(0, min(255, $r)); + $g = max(0, min(255, $g)); + $b = max(0, min(255, $b)); + return sprintf('#%02x%02x%02x', $r, $g, $b); + } +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/Prompt.php b/wp-content/plugins/postsecret-ai/src/Prompt.php new file mode 100644 index 0000000..3ad7dec --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/Prompt.php @@ -0,0 +1,316 @@ +2000 chars, truncate at 2000 and append ` … [TRUNCATED]`. + +--- + +## Source of truth + +* The **image is the source of truth**. +* Populate `front` and `back` from their respective images only. +* If the back image is missing or unreadable, set `back` to `null`. + +--- + +## OUTPUT SCHEMA (exact key order) + +{ + "tags": ["", "..."], + "secretDescription": "", + "media": { + "type": "", + "defects": { + "overall": { + "sharpness": "", + "exposure": "", + "colorCast": "", + "severity": "", + "notes": "" + }, + "defects": [ + { + "code": "", + "severity": "", + "coverage": 0.00, + "confidence": 0.00, + "region": { "x": 0.00, "y": 0.00, "w": 0.00, "h": 0.00 }, + "where": "" + } + ] + }, + "defectSummary": "<≤120 chars; one clause summarizing top issues or empty string>" + }, + "front": { + "artDescription": "<12–30 words on front visual style and notable elements>", + "fontDescription": { + "style": "", + "notes": "" + }, + "text": { + "fullText": "" or null, + "language": "", + "handwriting": true + } + }, + "back": { + "artDescription": "<12–30 words on back visual style and notable elements>", + "fontDescription": { + "style": "", + "notes": "" + }, + "text": { + "fullText": "" or null, + "language": "", + "handwriting": false + } + }, + "moderation": { + "reviewStatus": "", + "labels": ["", "..."], + "nsfwScore": 0.00, + "containsPII": false, + "piiTypes": [] + }, + "confidence": { + "overall": 0.00, + "byField": { + "tags": 0.00, + "media.defects": 0.00, + "artDescription": 0.00, + "fontDescription": 0.00, + "moderation": 0.00 + } + } +} + +--- + +Here’s a clean, AI-first tag spec focused on **topics, meaning, and feelings**—no materials, colors, or layout/style. + +# Tags (Global, High-Signal) + +## Purpose + +Provide concise, searchable labels that help curators and readers find Secrets by **topic** (what it’s about), **meaning** (what it says/teaches), and **feeling** (how it sounds). Avoid surface/visual tags. + +## Output requirements + +* **Count:** 3–8 total tags. +* **Mix:** **2–4 themes** + **0–2 tones**. +* **Format:** `lower_snake_case`, unique, **lexicographically sorted**. +* **Scope:** Reflect the **overall** Secret (front and back combined). No PII. + +--- + +## Theme Categories (topics & meaning) + +Pick the most specific themes that clearly fit. If nothing specific is evident, you may use one generic theme (e.g., a generic “confession/secrets” concept). + +1. **Relationships & Family** + Romantic dynamics, breakups/divorce, parenting, pregnancy, family roles, betrayals, friendships, attachment/loneliness. + +2. **Identity & Belonging** + Self-concept, social belonging/outsider feelings, values/faith/doubt, presentation, acceptance vs. concealment. + +3. **Health & Mind** + Physical/mental health experiences, disability, coping, grief/loss, substance use and recovery, fear/stress. + +4. **Life Stages & Pressure** + School/work pressures, money/poverty/debt, aging, ambition, regret, shame/guilt about life choices. + +5. **Acts & Events** + Confessions, transgressions, making amends, coming out/reveals, major life events (moves, weddings, funerals), consequences. + +6. **Insight (wisdom/lesson/learning)** + Lessons learned, cautions/warnings, advice offered, growth/acceptance/forgiveness/redemption, resilience/resolve. + +> You may coin a short, concrete theme within one category when needed. Keep it broadly useful (no PII; avoid niche jargon). + +--- + +## Tone Categories (feelings & stance) + +Add up to **two** tones if emotion is clear from language or unmistakable context. Otherwise, omit tones. + +* **Contrition/Responsibility** (e.g., remorse, guilt, apology) +* **Hope/Resolve** (e.g., hopeful, accepting, determined) +* **Pain/Distress** (e.g., despairing, anxious, overwhelmed) +* **Anger/Defiance** (e.g., angry, bitter, defiant) +* **Nostalgia/Sadness** (e.g., wistful, nostalgic, lonely) +* **Disclosure/Stance** (e.g., confessional, conflicted, relieved) + +--- + +## Tag Shape & Style + +* **Form:** short nouns/gerunds; 1–3 words joined by underscores. +* **Generalizable:** broadly useful to curators/readers; avoid hyper-specific one-offs. +* **Examples (schematic only):** + + * Themes: `relationship_topic`, `family_dynamic`, `work_pressure`, `financial_stress`, `identity_reveal`, `grief_event`, `life_lesson` + * Tones: `remorseful_tone`, `defiant_tone`, `hopeful_tone`, `nostalgic_tone` + +--- + +## Selection heuristics (flexible, not rigid) + +1. **Themes first.** + Choose **2–4** themes that are explicit or unmistakable from text or imagery. Prefer **specific** over generic (`infidelity` > `love`). If nothing specific, use exactly one fallback: `secrets` **or** `confession`. + +2. **Insight when present.** + If the Secret teaches/reflects/advices, include **up to two** Insight tags (e.g., `life_lesson`, `cautionary`, `personal_growth`, `wisdom`). Look for cues like “I learned…”, “If I could tell you…”, “Don’t…”, “I realized…”. + +3. **Tones are optional.** + Add **0–2** tones when emotion is clear (e.g., “I’m so sorry” → `remorseful`; “I’m done” → `resigned`; “I forgive you” → `forgiving`). If uncertain, omit rather than guess. + +4. **Front/back reconciliation.** + Merge evidence from both sides, dedupe, and keep the **clearest** themes. For tones, keep at most **two** that best capture the overall feeling. + +5. **Signal over noise.** + Every tag should help retrieval or curation. Drop decorative or redundant choices. Stay within **3–6** total. + +6. **Safety & PII.** + Never create tags that include names, addresses, contact details, usernames, or doxxing hints. Don’t assign clinical diagnoses unless **explicitly** stated; prefer emotional tones instead. + +7. **Formatting checks.** + Lowercase, underscores for spaces, sort lexicographically, no duplicates. + +--- + +## Side rules (`front`, `back`) + +* `artDescription`: describe the side’s visual style/elements (12–30 words). Objective, non-PII. +* `fontDescription.style`: `handwritten`, `typed`, `stenciled`, `mixed`, or `unknown` as seen **on that side**. +* `text.handwriting`: `true` iff any visible text on that side is handwritten. +* `text.fullText`: transcribe exactly what is visible; normalize whitespace; preserve distinct line breaks as `\n`; no spelling/case correction. + +--- + +## Defects (global) + +* Report at `media.defects` for the overall submission (not per side). +* Include up to **3** entries, ranked by **severity** then **coverage** (prefer legibility-impacting issues: `glare_reflection`, soft focus, `skew`, `crop_cutoff`). +* `defectSummary`: one clause, ≤120 chars. + +--- + +## Moderation (global) + +Decision order: + +1. **`reject_candidate`** — only if clearly present; add labels: + + * `sexual_content` + `minors_context` (sexual content involving minors) + * `sexual_content` (graphic adult sex/how-to) + * `ncii` (+ `pii_present_strong` if PII) + * `threat` (credible, imminent: target + intent + means/time/place) + * `illicit_instructions` + * `extremism_promotion` + * `self_harm_instructions` + * Severe doxxing enabling harm → `pii_present_strong`, `targeted_harassment` + * `hate_violence` (violence/dehumanization) + * `sexual_violence` + * `fraud_malware` + * If uncertain → do **not** reject; use `needs_review`. +2. **`needs_review`** — any serious label or `nsfwScore ≥ 0.70` (e.g., `self_harm_mention`, non-imminent `threat`, `slur_present`, non-graphic adult `sexual_content`, `minors_context`, `pii_present_strong`). +3. **`auto_vetted`** — none of the above and `nsfwScore < 0.70`. + +**PII**: `containsPII=true` only for clear **name**, **email**, **phone**, or **postal address**; set `piiTypes` from `[name,email,phone,address,other]`. Initials or generic roles do **not** count. + +--- + +## Confidence (global) + +Set `confidence.byField` individually (0.00–1.00), then compute `confidence.overall` as weighted mean: + +* `tags` 0.20, `media.defects` 0.20, `artDescription` 0.15, `fontDescription` 0.15, `moderation` 0.30. + +Rubric: **0.90–1.00** crisp/unambiguous; **0.60–0.89** minor ambiguity; **0.30–0.59** multiple uncertainties; **<0.30** largely unreadable. + +--- + +## Defaults (when side missing or unreadable) + +{ + "tags": [], + "secretDescription": "", + "media": { + "type": "unknown", + "defects": { + "overall": { + "sharpness": "unknown", + "exposure": "unknown", + "colorCast": "unknown", + "severity": "unknown", + "notes": "" + }, + "defects": [] + }, + "defectSummary": "" + }, + "front": { + "artDescription": "", + "fontDescription": { "style": "unknown", "notes": "" }, + "text": { "fullText": null, "language": "unknown", "handwriting": false } + }, + "back": { + "artDescription": "", + "fontDescription": { "style": "unknown", "notes": "" }, + "text": { "fullText": null, "language": "unknown", "handwriting": false } + }, + "moderation": { + "reviewStatus": "auto_vetted", + "labels": [], + "nsfwScore": 0.00, + "containsPII": false, + "piiTypes": [] + }, + "confidence": { + "overall": 0.00, + "byField": { + "tags": 0.00, + "media.defects": 0.00, + "artDescription": 0.00, + "fontDescription": 0.00, + "moderation": 0.00 + } + } +} +PROMPT; +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/Schema.php b/wp-content/plugins/postsecret-ai/src/Schema.php new file mode 100644 index 0000000..0b210f2 --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/Schema.php @@ -0,0 +1,78 @@ +> */ + public static function get(): array + { + return [ + // 1) OpenAI API + ['section' => 'api', 'order' => 10, 'key' => 'API_BASE', 'label' => 'API Base URL', 'kind' => 'str', 'default' => '', 'help' => 'Optional OpenAI-compatible base URL (leave empty for api.openai.com)'], + ['section' => 'api', 'order' => 20, 'key' => 'API_KEY', 'label' => 'API Key', 'kind' => 'str', 'default' => '', 'secret' => true, 'help' => 'Your OpenAI API key'], + + // 2) Model & Generation + ['section' => 'model', 'order' => 10, 'key' => 'MODEL_PROVIDER', 'label' => 'Model Provider', 'kind' => 'choice', 'default' => 'openai', 'choices' => ['openai'], 'help' => 'Provider (fixed to OpenAI for MVP)'], + ['section' => 'model', 'order' => 20, 'key' => 'MODEL_NAME', 'label' => 'Model Name', 'kind' => 'str', 'default' => 'gpt-4o-mini', 'help' => 'Vision-capable model'], + ['section' => 'model', 'order' => 40, 'key' => 'TEMPERATURE', 'label' => 'Temperature', 'kind' => 'float', 'default' => 0.2, 'min' => 0.0, 'max' => 2.0, 'help' => 'Creativity (0.0–2.0)'], + ['section' => 'model', 'order' => 50, 'key' => 'TOP_P', 'label' => 'Top-p', 'kind' => 'float', 'default' => 1.0, 'min' => 0.0, 'max' => 1.0, 'help' => 'Nucleus sampling (0.0–1.0)'], + ['section' => 'model', 'order' => 60, 'key' => 'MAX_TOKENS', 'label' => 'Max Tokens', 'kind' => 'int', 'default' => 1200, 'min' => 1, 'help' => 'Token limit per call'], + + // 3) Moderation + ['section' => 'moderation', 'order' => 10, 'key' => 'MODERATION_ENABLE', 'label' => 'Enable Moderation', 'kind' => 'bool', 'default' => false, 'help' => 'Run an additional moderation check'], + ['section' => 'moderation', 'order' => 20, 'key' => 'MODERATION_MODEL', 'label' => 'Moderation Model', 'kind' => 'str', 'default' => 'omni-moderation-latest', 'help' => 'Model for moderation (when enabled)'], + + // 4) HTTP (timeouts & retries) + ['section' => 'http', 'order' => 10, 'key' => 'REQUEST_TIMEOUT_SECONDS', 'label' => 'HTTP Timeout (s)', 'kind' => 'int', 'default' => 60, 'min' => 1, 'help' => 'Per-request timeout'], + ['section' => 'http', 'order' => 20, 'key' => 'REQUEST_MAX_RETRIES', 'label' => 'HTTP Retries', 'kind' => 'int', 'default' => 3, 'min' => 0, 'help' => 'Retries on transient errors'], + ['section' => 'http', 'order' => 30, 'key' => 'REQUEST_BACKOFF_FACTOR', 'label' => 'HTTP Backoff Factor', 'kind' => 'float', 'default' => 0.5, 'min' => 0.0, 'max' => 10.0, 'help' => 'Delay multiplier between retries'], + + // 5) Logging + ['section' => 'logging', 'order' => 10, 'key' => 'LOG_LEVEL', 'label' => 'Log Level', 'kind' => 'choice', 'default' => 'INFO', 'choices' => ['DEBUG', 'INFO', 'WARN', 'ERROR'], 'help' => 'Controls plugin logging verbosity'], + + // 6) Encoding (WebP) + ['section' => 'encoding', 'order' => 10, 'key' => 'WEBP_ENABLE', 'label' => 'Save WebP', 'kind' => 'bool', 'default' => false, 'help' => 'Save WebP copies (requires WebP support on server)'], + ['section' => 'encoding', 'order' => 20, 'key' => 'WEBP_QUALITY', 'label' => 'WebP Quality', 'kind' => 'int', 'default' => 80, 'min' => 0, 'max' => 100, 'help' => 'Lossy quality (0–100)'], + ['section' => 'encoding', 'order' => 30, 'key' => 'WEBP_LOSSLESS', 'label' => 'WebP Lossless', 'kind' => 'bool', 'default' => false, 'help' => 'Use lossless compression'], + ['section' => 'encoding', 'order' => 40, 'key' => 'WEBP_METHOD', 'label' => 'WebP Method', 'kind' => 'int', 'default' => 4, 'min' => 0, 'max' => 6, 'help' => 'Encoder effort (0–6)'], + + // 7) Ingest (Future) + ['section' => 'ingest', 'order' => 10, 'key' => 'ALLOWED_EXT', 'label' => 'Allowed Extensions', 'kind' => 'str', 'default' => 'jpg,jpeg,png,webp,tif,tiff', 'help' => 'For future folder scanning'], + ['section' => 'ingest', 'order' => 20, 'key' => 'RECURSIVE', 'label' => 'Recursive', 'kind' => 'bool', 'default' => true, 'help' => 'Scan subdirectories (future)'], + ['section' => 'ingest', 'order' => 30, 'key' => 'FORCE', 'label' => 'Force Reprocess', 'kind' => 'bool', 'default' => false, 'help' => 'Reprocess even if outputs exist (future)'], + + // 8) Paths (Future) + ['section' => 'paths', 'order' => 10, 'key' => 'IMAGES_DIR', 'label' => 'Images Directory', 'kind' => 'path', 'default' => 'images', 'help' => 'Folder containing input images (future)', 'path_kind' => 'dir'], + ['section' => 'paths', 'order' => 20, 'key' => 'OUTPUT_DIR', 'label' => 'Output Directory', 'kind' => 'path', 'default' => 'output', 'help' => 'Folder for classification results (future)', 'path_kind' => 'dir'], + ]; + } + + /** Titles + descriptions for sections (rendered by Settings::register) */ + public static function sections(): array + { + return [ + 'api' => ['title' => 'OpenAI API', 'desc' => 'Connection settings for the OpenAI API.'], + 'model' => ['title' => 'Model & Generation', 'desc' => 'Choose the model and its generation parameters.'], + 'moderation' => ['title' => 'Moderation', 'desc' => 'Optional post-classification moderation.'], + 'http' => ['title' => 'HTTP (Timeouts & Retries)', 'desc' => 'Network behavior for API requests.'], + 'logging' => ['title' => 'Logging', 'desc' => 'Control plugin logging verbosity.'], + 'encoding' => ['title' => 'Encoding (WebP)', 'desc' => 'Optional WebP export (server support required).'], + 'ingest' => ['title' => 'Ingest (Future)', 'desc' => 'Reserved for future folder scanning.'], + 'paths' => ['title' => 'Paths (Future)', 'desc' => 'Reserved for future file-system workflows.'], + ]; + } +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/SchemaGuard.php b/wp-content/plugins/postsecret-ai/src/SchemaGuard.php new file mode 100644 index 0000000..578d4c9 --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/SchemaGuard.php @@ -0,0 +1,274 @@ + default block) + */ +final class SchemaGuard +{ + /** Default object for media.defects.overall */ + private const DEF_OVERALL = [ + 'sharpness' => 'unknown', + 'exposure' => 'unknown', + 'colorCast' => 'unknown', + 'severity' => 'unknown', + 'notes' => '', + ]; + + /** Default defect entry */ + private const DEF_DEFECT = [ + 'code' => 'other', + 'severity' => 'low', + 'coverage' => 0.00, + 'confidence' => 0.00, + 'region' => ['x' => 0.00, 'y' => 0.00, 'w' => 0.00, 'h' => 0.00], + 'where' => 'unknown', + ]; + + /** Default side block */ + private const DEF_SIDE = [ + 'artDescription' => '', + 'fontDescription' => ['style' => 'unknown', 'notes' => ''], + 'text' => ['fullText' => null, 'language' => 'unknown', 'handwriting' => false], + ]; + + /** Full defaults shape */ + private const DEF_PAYLOAD = [ + 'tags' => [], + 'secretDescription' => '', + 'media' => [ + 'type' => 'unknown', + 'defects' => [ + 'overall' => self::DEF_OVERALL, + 'defects' => [], + ], + 'defectSummary' => '', + ], + 'front' => self::DEF_SIDE, + 'back' => self::DEF_SIDE, + 'moderation' => [ + 'reviewStatus' => 'auto_vetted', + 'labels' => [], + 'nsfwScore' => 0.00, + 'containsPII' => false, + 'piiTypes' => [], + ], + 'confidence' => [ + 'overall' => 0.00, + 'byField' => [ + 'tags' => 0.00, + 'media.defects' => 0.00, + 'artDescription' => 0.00, + 'fontDescription' => 0.00, + 'moderation' => 0.00, + ], + ], + ]; + + /** Allowed enums */ + private const ENUMS = [ + 'media.type' => ['postcard', 'note_card', 'letter', 'photo', 'poster', 'mixed', 'unknown'], + 'sharpness' => ['sharp', 'soft', 'blurred', 'unknown'], + 'exposure' => ['under', 'normal', 'over', 'unknown'], + 'colorCast' => ['neutral', 'warm', 'cool', 'mixed', 'unknown'], + 'severity' => ['low', 'medium', 'high', 'unknown'], + 'defect.code' => ['crease_fold', 'glare_reflection', 'shadow', 'tear', 'stain', 'ink_bleed', 'noise', 'skew', 'crop_cutoff', 'moire', 'color_shift', 'other'], + 'where' => ['top_left', 'top', 'top_right', 'left', 'center', 'right', 'bottom_left', 'bottom', 'bottom_right', 'unknown'], + 'font.style' => ['handwritten', 'typed', 'stenciled', 'mixed', 'unknown'], + 'reviewStatus' => ['auto_vetted', 'needs_review', 'reject_candidate'], + 'lang' => null, // any ISO 639-1 or "unknown"; we just lowercase + 'piiTypes' => ['name', 'email', 'phone', 'address', 'other'], + ]; + + /** Public entry */ + public static function normalize($in): array + { + $p = is_array($in) ? $in : []; + + $out = self::DEF_PAYLOAD; + + // tags + $out['tags'] = self::norm_list($p['tags'] ?? [], maxLen: 8); + + // secretDescription + $out['secretDescription'] = self::norm_str($p['secretDescription'] ?? ''); + + // media + $out['media']['type'] = self::enum($p['media']['type'] ?? 'unknown', 'media.type'); + $ov = $p['media']['defects']['overall'] ?? []; + $out['media']['defects']['overall'] = [ + 'sharpness' => self::enum($ov['sharpness'] ?? 'unknown', 'sharpness'), + 'exposure' => self::enum($ov['exposure'] ?? 'unknown', 'exposure'), + 'colorCast' => self::enum($ov['colorCast'] ?? 'unknown', 'colorCast'), + 'severity' => self::enum($ov['severity'] ?? 'unknown', 'severity'), + 'notes' => self::norm_str($ov['notes'] ?? ''), + ]; + $defArr = is_array($p['media']['defects']['defects'] ?? null) ? $p['media']['defects']['defects'] : []; + $out['media']['defects']['defects'] = self::norm_defects($defArr, 3); + $out['media']['defectSummary'] = self::truncate_chars(self::norm_str($p['media']['defectSummary'] ?? ''), 120); + + // front & back blocks + $out['front'] = self::norm_side($p['front'] ?? null); + // Allow null back per prompt; coerce null -> default block for storage + $out['back'] = is_null($p['back'] ?? null) ? self::DEF_SIDE : self::norm_side($p['back']); + + // moderation + $m = $p['moderation'] ?? []; + $out['moderation'] = [ + 'reviewStatus' => self::enum($m['reviewStatus'] ?? 'auto_vetted', 'reviewStatus'), + 'labels' => self::norm_list($m['labels'] ?? [], maxLen: 20), + 'nsfwScore' => self::f01($m['nsfwScore'] ?? 0.00), + 'containsPII' => (bool)($m['containsPII'] ?? false), + 'piiTypes' => self::norm_list($m['piiTypes'] ?? [], allowed: self::ENUMS['piiTypes']), + ]; + + // confidence + $c = $p['confidence'] ?? []; + $bf = $c['byField'] ?? []; + $out['confidence'] = [ + 'overall' => self::f01($c['overall'] ?? 0.00), + 'byField' => [ + 'tags' => self::f01($bf['tags'] ?? 0.00), + 'media.defects' => self::f01($bf['media.defects'] ?? 0.00), + 'artDescription' => self::f01($bf['artDescription'] ?? 0.00), + 'fontDescription' => self::f01($bf['fontDescription'] ?? 0.00), + 'moderation' => self::f01($bf['moderation'] ?? 0.00), + ], + ]; + + return $out; + } + + /** -------- helpers -------- */ + + private static function norm_side($side): array + { + if (!is_array($side)) return self::DEF_SIDE; + + // artDescription + $art = self::norm_str($side['artDescription'] ?? ''); + // fontDescription + $fd = $side['fontDescription'] ?? []; + $font = [ + 'style' => self::enum($fd['style'] ?? 'unknown', 'font.style'), + 'notes' => self::norm_str($fd['notes'] ?? ''), + ]; + // text + $tx = $side['text'] ?? []; + $full = array_key_exists('fullText', $tx) ? $tx['fullText'] : null; + $full = is_string($full) ? self::norm_text($full, 2000) : null; + $lang = strtolower(self::norm_str($tx['language'] ?? 'unknown')); + if ($lang === '') $lang = 'unknown'; + $hand = (bool)($tx['handwriting'] ?? false); + + return [ + 'artDescription' => $art, + 'fontDescription' => $font, + 'text' => [ + 'fullText' => $full, + 'language' => $lang, + 'handwriting' => $hand, + ], + ]; + } + + private static function norm_defects(array $arr, int $limit): array + { + $out = []; + foreach ($arr as $row) { + if (!is_array($row)) continue; + $d = self::DEF_DEFECT; + $d['code'] = self::enum($row['code'] ?? 'other', 'defect.code'); + $d['severity'] = self::enum($row['severity'] ?? 'low', 'severity'); + $d['coverage'] = self::f01($row['coverage'] ?? 0.00); + $d['confidence'] = self::f01($row['confidence'] ?? 0.00); + $r = $row['region'] ?? []; + $d['region'] = [ + 'x' => self::f01($r['x'] ?? 0.00), + 'y' => self::f01($r['y'] ?? 0.00), + 'w' => self::f01($r['w'] ?? 0.00), + 'h' => self::f01($r['h'] ?? 0.00), + ]; + $d['where'] = self::enum($row['where'] ?? 'unknown', 'where'); + $out[] = $d; + if (count($out) >= $limit) break; + } + return $out; + } + + private static function enum($v, string $key): string + { + $v = is_string($v) ? strtolower(trim($v)) : ''; + $allowed = self::ENUMS[$key] ?? null; + if ($allowed === null) { + // open set (language) + return $v !== '' ? $v : 'unknown'; + } + return in_array($v, $allowed, true) ? $v : 'unknown'; + } + + private static function f01(mixed $n): float + { + $x = is_numeric($n) ? (float)$n : 0.00; + if ($x < 0.0) $x = 0.0; + if ($x > 1.0) $x = 1.0; + return round($x, 2); + } + + private static function norm_str(mixed $s): string + { + if (!is_string($s)) return ''; + $s = preg_replace('/\s+/u', ' ', trim($s)); + return is_string($s) ? $s : ''; + } + + private static function norm_text(string $s, int $maxChars): string + { + $s = str_replace(["\r\n", "\r"], "\n", $s); + $s = preg_replace("/[ \t]+/u", ' ', $s); + $s = trim($s); + if (mb_strlen($s, 'UTF-8') > $maxChars) { + $s = mb_substr($s, 0, $maxChars, 'UTF-8') . ' … [TRUNCATED]'; + } + return $s; + } + + /** + * Normalize a list of strings: lowercase, trim, dedupe, sort, optionally filter to allowed set, limit length. + * @param mixed $arr + * @param int $maxLen + * @param array|null $allowed + * @return array + */ + private static function norm_list(mixed $arr, int $maxLen = 50, ?array $allowed = null): array + { + if (!is_array($arr)) $arr = []; + $norm = []; + foreach ($arr as $t) { + if (!is_string($t)) continue; + $x = strtolower(trim($t)); + if ($x === '') continue; + if ($allowed && !in_array($x, $allowed, true)) continue; + $norm[$x] = true; + if (count($norm) >= $maxLen) break; + } + $out = array_keys($norm); + sort($out, SORT_STRING); + return $out; + } + + private static function truncate_chars(string $s, int $limit): string + { + if (mb_strlen($s, 'UTF-8') <= $limit) return $s; + return mb_substr($s, 0, $limit, 'UTF-8'); + } +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-ai/src/Settings.php b/wp-content/plugins/postsecret-ai/src/Settings.php new file mode 100644 index 0000000..5605718 --- /dev/null +++ b/wp-content/plugins/postsecret-ai/src/Settings.php @@ -0,0 +1,165 @@ + 'array', 'sanitize_callback' => [self::class, 'sanitizeAll']] + ); + + // Create sections in logical order + $sections = Schema::sections(); + foreach (['api', 'model', 'moderation', 'http', 'logging', 'encoding', 'ingest', 'paths'] as $sid) { + if (!isset($sections[$sid])) continue; + add_settings_section( + 'psai_' . $sid, + esc_html($sections[$sid]['title']), + function () use ($sections, $sid) { + echo '

' . esc_html($sections[$sid]['desc']) . '

'; + }, + 'postsecret-ai' + ); + } + + // Add fields grouped by section, ordered by 'order' + $specs = Schema::get(); + usort($specs, function ($a, $b) { + return [$a['section'], $a['order'] ?? 999] <=> [$b['section'], $b['order'] ?? 999]; + }); + + foreach ($specs as $spec) { + add_settings_field( + $spec['key'], + esc_html($spec['label']), + [self::class, 'renderField'], + 'postsecret-ai', + 'psai_' . $spec['section'], + ['spec' => $spec] + ); + } + } + + /** Sanitize the full array */ + public static function sanitizeAll($input) + { + $out = []; + $defs = self::defaults(); + + foreach (Schema::get() as $spec) { + $k = $spec['key']; + $v = $input[$k] ?? $defs[$k]; + $out[$k] = self::sanitizeOne($spec, $v); + } + + // --- Minimal validation & user feedback --- + if (empty($out['API_KEY'])) { + add_settings_error('postsecret-ai', 'psai_api_key_missing', 'API Key is required to call OpenAI.', 'error'); + } + if (!empty($out['REQUEST_TIMEOUT_SECONDS']) && (int)$out['REQUEST_TIMEOUT_SECONDS'] < 5) { + add_settings_error('postsecret-ai', 'psai_timeout_low', 'HTTP Timeout seems low; consider ≥ 5 seconds.', 'warning'); + } + if (!empty($out['MODEL_NAME']) && !is_string($out['MODEL_NAME'])) { + add_settings_error('postsecret-ai', 'psai_model_bad', 'Model Name must be a string.', 'error'); + } + + // You could block saving by returning the old value when critical errors occur. + // For now we still save, but show errors/warnings. + return $out; + } + + /** @return array defaults by key */ + public static function defaults(): array + { + $d = []; + foreach (Schema::get() as $spec) $d[$spec['key']] = $spec['default'] ?? ''; + return $d; + } + + /** Sanitize a single field based on kind + min/max */ + private static function sanitizeOne(array $spec, $v) + { + $kind = $spec['kind']; + switch ($kind) { + case 'bool': + $val = (bool)$v; + break; + case 'int': + $val = is_numeric($v) ? (int)$v : (int)($spec['default'] ?? 0); + break; + case 'float': + $val = is_numeric($v) ? (float)$v : (float)($spec['default'] ?? 0.0); + break; + case 'choice': + $choices = $spec['choices'] ?? []; + $val = in_array($v, $choices, true) ? $v : ($spec['default'] ?? ($choices[0] ?? '')); + break; + case 'path': + case 'str': + default: + $val = is_string($v) ? trim($v) : ''; + break; + } + if (isset($spec['min']) && is_numeric($spec['min']) && is_numeric($val)) $val = max($val, $spec['min']); + if (isset($spec['max']) && is_numeric($spec['max']) && is_numeric($val)) $val = min($val, $spec['max']); + return $val; + } + + /** Render a single field row */ + public static function renderField(array $args): void + { + $spec = $args['spec']; + $key = $spec['key']; + $val = get_option(self::OPTION, []); + $cur = $val[$key] ?? ($spec['default'] ?? ''); + + $name = self::OPTION . '[' . esc_attr($key) . ']'; + $desc = !empty($spec['help']) ? '

' . esc_html($spec['help']) . '

' : ''; + $secret = !empty($spec['secret']); + $choices = $spec['choices'] ?? []; + + switch ($spec['kind']) { + case 'bool': + echo '' . $desc; + break; + + case 'choice': + echo '' . $desc; + break; + + case 'int': + case 'float': + $step = $spec['kind'] === 'int' ? '1' : 'any'; + $min = isset($spec['min']) ? ' min="' . esc_attr($spec['min']) . '"' : ''; + $max = isset($spec['max']) ? ' max="' . esc_attr($spec['max']) . '"' : ''; + echo ''; + echo $desc; + break; + + case 'path': + echo ''; + $hint = !empty($spec['path_kind']) ? ' (' . $spec['path_kind'] . ')' : ''; + echo '

Path' . $hint . '. ' . $spec['help'] . '

'; + break; + + case 'str': + default: + $type = $secret ? 'password' : 'text'; + echo ''; + echo $desc; + break; + } + } +} \ No newline at end of file diff --git a/wp-content/plugins/postsecret-feed/assets/psai-stream.js b/wp-content/plugins/postsecret-feed/assets/psai-stream.js new file mode 100644 index 0000000..fd57b8c --- /dev/null +++ b/wp-content/plugins/postsecret-feed/assets/psai-stream.js @@ -0,0 +1,413 @@ +/* psai-stream.js + * Infinite postcard stream for the home page. + * - Pulls vetted “front” attachments via REST (pretty or legacy ?rest_route) + * - Newest-first, paged + * - Lazy images + IO prefetch + * - Light windowing (prunes far-off cards) + * - Renders cards via Mustache templates (#psai-card-tpl), falls back to DOM if missing + */ + +(() => { + // ----- Config ----- + const CFG = window.PSAI_STREAM || {}; + const API_PRIMARY = CFG.endpoint || ''; // e.g. /wp-json/psai/v1/secrets + const API_FALLBACK = CFG.endpointLegacy || ''; // e.g. /?rest_route=/psai/v1/secrets + const PER = Number(CFG.perPage || 24); + const MOUNT_ID = CFG.mountId || 'psai-stream'; + + // Soft windowing limits + const MAX_DOM_CARDS = 240; // ~10 pages @ 24 each + const PRUNE_BATCH = 60; + + // ----- Mount (self-heal if missing) ----- + let root = document.getElementById(MOUNT_ID); + if (!root) { + root = document.createElement('div'); + root.id = MOUNT_ID; + (document.querySelector('.ps-latest, main, body') || document.body).appendChild(root); + } + + // If no endpoint info at all, bail loudly + if (!API_PRIMARY && !API_FALLBACK) { + console.warn('PSAI: missing REST endpoints (endpoint / endpointLegacy).'); + return; + } + + // ----- UI shell ----- + root.innerHTML = ''; + const list = document.createElement('div'); + list.className = 'ps-stream'; + root.appendChild(list); + + const status = document.createElement('div'); + status.className = 'ps-status'; + status.style.textAlign = 'center'; + status.style.margin = '24px 0'; + status.style.color = 'var(--wp--preset--color--muted, #6b7280)'; + root.appendChild(status); + + const sentinel = document.createElement('div'); + sentinel.style.height = '1px'; + root.appendChild(sentinel); + + // ----- Templates ----- + const TPL = { + card: document.getElementById('psai-card-tpl')?.innerHTML || '', + skeleton: document.getElementById('psai-skeleton-tpl')?.innerHTML || '', + error: document.getElementById('psai-error-tpl')?.innerHTML || '' + }; + + const hasMustache = typeof window.Mustache !== 'undefined' && !!TPL.card; + + // ----- State ----- + let page = 1; + let totalPages = Infinity; + let loading = false; + const failedPages = new Set(); + + const setStatus = (t) => { + status.textContent = t || ''; + }; + + const fmtDate = (iso) => { + if (!iso) return ''; + try { + return new Date(iso).toLocaleDateString(undefined, {year: 'numeric', month: 'short', day: 'numeric'}); + } catch { + return ''; + } + }; + + // Data -> view-model transform for templates + const asViewModel = (item) => { + const tags = Array.isArray(item.tags) ? item.tags : []; + // Replace underscores with spaces in tag names + const displayTags = tags.slice(0, 3).map(tag => tag.replace(/_/g, ' ')); + const overflowCount = Math.max(0, tags.length - displayTags.length); + const alt = (item.alt || '').trim(); + const excerpt = (item.excerpt || '').trim(); + const hasBack = !!(item.back_id && item.back_src); + + if (hasBack) { + console.log('Card with back detected:', item.id, 'back_id:', item.back_id, 'back_src:', item.back_src); + } + + return { + ...item, + alt, + altFallback: alt || 'View secret', + dateFmt: fmtDate(item.date), + displayTags, + overflowCount: overflowCount || null, + advisory: false, // TODO: wire to API if/when content advisories are available + excerpt, + hasBack, + back_src: item.back_src || '', + back_alt: item.back_alt || '' + }; + }; + + // DOM fallback (if Mustache/template missing) + const createCardDOM = (item) => { + const card = document.createElement('article'); + card.className = 'ps-card'; + card.dataset.id = item.id; + card.dataset.orientation = item.orientation || 'unknown'; + + const a = document.createElement('a'); + a.href = item.link || '#'; + a.className = 'ps-card__link'; + a.setAttribute('aria-label', (item.alt || 'View secret')); + + const fig = document.createElement('figure'); + fig.className = 'ps-card__media'; + + const img = document.createElement('img'); + img.loading = 'lazy'; + img.decoding = 'async'; + img.src = item.src; + img.alt = item.alt || ''; + img.className = 'ps-card__img'; + fig.appendChild(img); + a.appendChild(fig); + + const meta = document.createElement('div'); + meta.className = 'ps-card__meta'; + + const date = document.createElement('div'); + date.className = 'ps-card__date'; + date.style.fontSize = '.85rem'; + date.style.color = 'var(--wp--preset--color--muted, #6b7280)'; + date.textContent = fmtDate(item.date); + meta.appendChild(date); + + if (item.excerpt) { + const p = document.createElement('p'); + p.className = 'ps-card__excerpt'; + p.textContent = item.excerpt; + meta.appendChild(p); + } + + if (Array.isArray(item.tags) && item.tags.length) { + const cap = document.createElement('div'); + cap.className = 'ps-card__tags'; + item.tags.slice(0, 3).forEach(t => { + const chip = document.createElement('span'); + chip.className = 'ps-chip'; + // Replace underscores with spaces + chip.textContent = t.replace(/_/g, ' '); + cap.appendChild(chip); + }); + const overflow = item.tags.length - 3; + if (overflow > 0) { + const chip = document.createElement('span'); + chip.className = 'ps-chip ps-chip--more'; + chip.textContent = `+${overflow}`; + cap.appendChild(chip); + } + meta.appendChild(cap); + } + + card.appendChild(a); + card.appendChild(meta); + + return card; + }; + + // Track min long side dimension from API data + let minLongSide = Infinity; + + // Calculate size styles based on API dimensions + const calculateCardStyles = (item) => { + const width = item.width || 0; + const height = item.height || 0; + const orientation = item.orientation || 'unknown'; + const longSide = Math.max(width, height); + + // Update global min + if (longSide > 0 && longSide < minLongSide) { + minLongSide = longSide; + } + + // Use min long side as target (never scale up) + const targetLongSide = minLongSide === Infinity ? longSide : minLongSide; + + let styles = {}; + if (orientation === 'landscape' && width && height) { + // For landscape: constrain width, calculate height from aspect ratio + const scaledHeight = (targetLongSide / width) * height; + styles.maxHeight = `${scaledHeight + 32}px`; // +32 for padding + styles.maxWidth = `${targetLongSide}px`; + } else if (orientation === 'portrait' && width && height) { + // For portrait: constrain height, calculate width from aspect ratio + const scaledWidth = (targetLongSide / height) * width; + styles.maxWidth = `${scaledWidth + 48}px`; // +48 for padding + styles.maxHeight = `${targetLongSide}px`; + } + + return styles; + }; + + // Apply calculated styles to cards + const normalizeSizes = () => { + const cards = list.querySelectorAll('.ps-card'); + cards.forEach(card => { + const media = card.querySelector('.ps-card__media'); + if (!media) return; + + const width = parseInt(card.dataset.width) || 0; + const height = parseInt(card.dataset.height) || 0; + const orientation = card.dataset.orientation || 'unknown'; + + if (!width || !height) return; + + const item = { width, height, orientation }; + const styles = calculateCardStyles(item); + + Object.entries(styles).forEach(([prop, value]) => { + media.style[prop] = value; + }); + }); + }; + + // Render helpers + const renderCards = (items) => { + // Update min long side from API data + items.forEach(item => { + const longSide = Math.max(item.width || 0, item.height || 0); + if (longSide > 0 && longSide < minLongSide) { + minLongSide = longSide; + } + }); + + const frag = document.createDocumentFragment(); + if (hasMustache) { + for (const raw of items) { + const html = window.Mustache.render(TPL.card, asViewModel(raw)); + const wrap = document.createElement('div'); + wrap.innerHTML = html; + const el = wrap.firstElementChild; + if (el) frag.appendChild(el); + } + } else { + for (const raw of items) frag.appendChild(createCardDOM(raw)); + } + list.appendChild(frag); + + // Normalize sizes based on API dimensions + normalizeSizes(); + + // Attach flip handlers to newly rendered cards + attachFlipHandlers(); + }; + + // Flip card handler + const attachFlipHandlers = () => { + const flipBtns = list.querySelectorAll('.ps-card__flip-btn'); + flipBtns.forEach(btn => { + if (btn.dataset.attached) return; // Already attached + btn.dataset.attached = 'true'; + + btn.addEventListener('click', (e) => { + e.preventDefault(); + e.stopPropagation(); + const card = btn.closest('.ps-card'); + if (card) { + card.classList.toggle('ps-card--flipped'); + } + }); + }); + }; + + const showSkeletons = () => { + if (!TPL.skeleton) return []; + const shells = []; + const count = Math.min(PER, 8); + for (let i = 0; i < count; i++) { + const wrap = document.createElement('div'); + wrap.innerHTML = TPL.skeleton; + const el = wrap.firstElementChild; + if (el) { + list.appendChild(el); + shells.push(el); + } + } + return shells; + }; + + const showError = () => { + if (TPL.error) { + status.innerHTML = TPL.error; + } else { + setStatus('Could not load more secrets right now.'); + } + }; + + const pruneDomIfNeeded = () => { + const count = list.children.length; + if (count <= MAX_DOM_CARDS) return; + const first = list.firstElementChild; + if (!first) return; + const rect = first.getBoundingClientRect(); + const buffer = -window.innerHeight * 2; + if (rect.bottom < buffer) { + let removed = 0; + while (removed < PRUNE_BATCH && list.firstElementChild) { + list.removeChild(list.firstElementChild); + removed++; + } + } + }; + + // ----- REST with fallback (/wp-json … then ?rest_route=) ----- + const buildUrl = (base, p, per) => { + const u = new URL(base, window.location.href); + u.searchParams.set('page', String(p)); + u.searchParams.set('per_page', String(per)); + return u.toString(); + }; + + const fetchPage = async (p, per) => { + // Try pretty + if (API_PRIMARY) { + try { + const r = await fetch(buildUrl(API_PRIMARY, p, per), {credentials: 'same-origin'}); + if (r.ok) return r.json(); + } catch { + } + } + // Fallback to legacy + if (API_FALLBACK) { + const r2 = await fetch(buildUrl(API_FALLBACK, p, per), {credentials: 'same-origin'}); + if (r2.ok) return r2.json(); + } + throw new Error('REST unavailable (pretty & legacy failed)'); + }; + + // ----- Loader ----- + async function load() { + if (loading || page > totalPages) return; + loading = true; + setStatus('Loading…'); + + const shells = showSkeletons(); + + try { + const data = await fetchPage(page, PER); + totalPages = Number.isFinite(data.total_pages) ? data.total_pages : 1; + const items = Array.isArray(data.items) ? data.items : []; + + renderCards(items); + + page += 1; + setStatus(''); + pruneDomIfNeeded(); + } catch (err) { + console.error('PSAI stream error:', err); + if (!failedPages.has(page)) { + failedPages.add(page); + setTimeout(() => { + loading = false; + load(); + }, 1200); + return; + } else { + showError(); + } + } finally { + shells.forEach(el => el.remove()); + loading = false; + } + } + + // ----- Infinite scroll ----- + const io = new IntersectionObserver((entries) => { + entries.forEach(e => { + if (e.isIntersecting) load(); + }); + }, {rootMargin: '1200px 0px'}); + io.observe(sentinel); + + // Kickoff + console.log('PSAI stream boot', {API_PRIMARY, API_FALLBACK, PER, MOUNT_ID, hasMustache}); + console.log('Mustache available:', typeof window.Mustache, 'Template:', !!TPL.card); + if (TPL.card) { + console.log('Template content (first 200 chars):', TPL.card.substring(0, 200)); + } + load(); + + // Manual fallback + const btn = document.createElement('button'); + btn.type = 'button'; + btn.textContent = 'Load more'; + btn.style.display = 'inline-block'; + btn.style.margin = '8px auto'; + btn.style.padding = '8px 16px'; + btn.style.borderRadius = '999px'; + btn.style.border = '1px solid #d1d5db'; + btn.style.background = '#fff'; + btn.style.cursor = 'pointer'; + btn.setAttribute('aria-label', 'Load more secrets'); + btn.addEventListener('click', () => load()); + status.appendChild(btn); +})(); \ No newline at end of file diff --git a/wp-content/plugins/postsecret-feed/postsecret-feed.php b/wp-content/plugins/postsecret-feed/postsecret-feed.php new file mode 100644 index 0000000..9fea6bf --- /dev/null +++ b/wp-content/plugins/postsecret-feed/postsecret-feed.php @@ -0,0 +1,113 @@ + 'GET', + 'permission_callback' => '__return_true', + 'args' => [ + 'page' => ['type' => 'integer', 'default' => 1, 'minimum' => 1], + 'per_page' => ['type' => 'integer', 'default' => 24, 'minimum' => 1, 'maximum' => 60], + ], + 'callback' => function (\WP_REST_Request $req) { + $page = max(1, (int)$req['page']); + $pp = min(60, max(1, (int)$req['per_page'])); + $q = new \WP_Query([ + 'post_type' => 'attachment', + 'post_status' => 'inherit', + 'post_mime_type' => 'image', + 'orderby' => 'date', + 'order' => 'DESC', + 'paged' => $page, + 'posts_per_page' => $pp, + 'meta_query' => [ + ['key' => '_ps_side', 'value' => 'front', 'compare' => '='], + ['key' => '_ps_is_vetted', 'value' => '1', 'compare' => '='], + ], + ]); + + $items = []; + foreach ($q->posts as $p) { + $id = (int)$p->ID; + $src = wp_get_attachment_image_src($id, 'secret-card'); + if (!$src) $src = [wp_get_attachment_url($id), 0, 0, true]; + + // Get back side data if exists + $back_id = (int)(get_post_meta($id, '_ps_pair_id', true) ?: 0) ?: null; + $back_src = null; + $back_alt = null; + if ($back_id) { + $back_image = wp_get_attachment_image_src($back_id, 'secret-card'); + if ($back_image) { + $back_src = $back_image[0]; + } else { + $back_src = wp_get_attachment_url($back_id); + } + $back_alt = get_post_meta($back_id, '_wp_attachment_image_alt', true) ?: ''; + } + + $items[] = [ + 'id' => $id, + 'src' => $src[0], + 'width' => (int)$src[1], + 'height' => (int)$src[2], + 'alt' => get_post_meta($id, '_wp_attachment_image_alt', true) ?: '', + 'caption' => get_post_field('post_excerpt', $id) ?: '', + 'excerpt' => get_post_field('post_content', $id) ?: '', + 'date' => get_post_datetime($id)?->format('c'), + 'tags' => array_values((array)get_post_meta($id, '_ps_tags', true) ?: []), + 'primary' => get_post_meta($id, '_ps_primary_hex', true) ?: '', + 'orientation' => get_post_meta($id, '_ps_orientation', true) ?: '', + 'back_id' => $back_id, + 'back_src' => $back_src, + 'back_alt' => $back_alt, + 'link' => get_attachment_link($id), + ]; + } + + return new \WP_REST_Response([ + 'page' => $page, + 'per_page' => $pp, + 'total' => (int)$q->found_posts, + 'total_pages' => (int)$q->max_num_pages, + 'items' => $items, + ], 200); + } + ]); +}); + +// Front-page stream (only on home) +add_action('wp_enqueue_scripts', function () { + // load wherever you want — front page only once this works + if (is_admin()) return; + + $handle = 'psai-stream'; + wp_register_script($handle, plugins_url('assets/psai-stream.js', __FILE__), ['mustache'], null, true); + + // Pretty and legacy (query-param) endpoints + $pretty = rest_url('psai/v1/secrets'); + $legacy = add_query_arg('rest_route', '/psai/v1/secrets', site_url('/')); + + $cfg = [ + 'endpoint' => esc_url_raw($pretty), + 'endpointLegacy' => esc_url_raw($legacy), + 'perPage' => 24, + 'mountId' => 'psai-stream', + ]; + wp_add_inline_script($handle, 'window.PSAI_STREAM=' . wp_json_encode($cfg) . ';', 'before'); + wp_enqueue_script($handle); +}); \ No newline at end of file diff --git a/wp-content/themes/index.php b/wp-content/themes/index.php new file mode 100644 index 0000000..6220032 --- /dev/null +++ b/wp-content/themes/index.php @@ -0,0 +1,2 @@ + + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. + + Preamble + + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. diff --git a/wp-content/themes/ollie/README.md b/wp-content/themes/ollie/README.md new file mode 100644 index 0000000..bd5d77c --- /dev/null +++ b/wp-content/themes/ollie/README.md @@ -0,0 +1,196 @@ + +# Ollie — WordPress's Most Popular Block Theme for Full Site Editing + +![Image](https://user-images.githubusercontent.com/1415737/217930880-5d019715-f0c2-4f2f-9d24-dd466abf531b.jpg) + +**Design better, build faster, publish sooner.** Ollie is a blazing-fast WordPress block theme that makes professional website design accessible to everyone — no coding or expensive page builders required. + +## ✨ Why Choose Ollie? + +- **🚀 Lightning Fast**: Scores 100% on Core Web Vitals with optimized performance out of the box +- **🎨 50+ Beautiful Patterns**: Pre-designed sections and full-page layouts for instant professional designs +- **🎯 True No-Code Solution**: Seamlessly integrated with WordPress's native site editor — drag, drop, and publish +- **📱 Mobile-First Design**: Every pattern and layout is responsive and optimized for all devices +- **🛠️ Developer Friendly**: Clean, semantic code with modern build tools and translation support +- **🎪 Endless Customization**: 7 style variations, 9 typography presets, and unlimited color combinations + +Built by [Mike McAlister](https://mikemcalister.com) and [Patrick Posner](https://patrickposner.com), Ollie empowers both beginners and professionals to create stunning WordPress websites using the latest Full Site Editing features. + +## 🚀 Quick Start + +| Action | Link | +| --- | --- | +| **View Demo** | [demo.olliewp.com](https://demo.olliewp.com) | +| **Download Theme** | [Latest Release](https://github.com/OllieWP/ollie/releases/latest/download/ollie.zip) | +| **Child Theme** | [Download Child Theme](https://github.com/OllieWP/ollie-child/releases/latest/download/ollie-child.zip) | +| **Documentation** | [olliewp.com/docs](https://olliewp.com/docs) | + +### Requirements +- WordPress 6.0 or later +- PHP 7.2 or later + +## 📦 Installation + +Ollie is available directly from the WordPress theme directory: + +1. In your WordPress admin, go to **Appearance → Themes → Add New** +2. Search for "Ollie" +3. Click **Install** and then **Activate** +4. Start designing in **Appearance → Editor** + +## 🎨 Features & Capabilities + +### Pattern Library +Ollie includes **50+ professionally designed patterns** organized into categories: + +- **Full Page Layouts**: Home, About, Features, Pricing, Blog, Contact, Profile +- **Headers & Footers**: Light/Dark variations with different layouts +- **Hero Sections**: Multiple styles with calls-to-action +- **Content Cards**: Testimonials, pricing tables, team members, FAQs +- **Feature Sections**: Service boxes, numbered features, icon grids +- **Blog Components**: Post grids, author boxes, comment sections + +### Style Variations +Switch your entire site's look with one click: + +- **Default**: Clean, modern design +- **Agency**: Bold, professional styling +- **Creator**: Content-focused layout +- **Startup**: Tech-inspired design +- **Studio**: Minimal, elegant aesthetic + +### Color Palettes +Pre-configured color schemes: +- Blue, Green, Neon, Orange, Pink, Red, Teal + +### Typography System +**9 typography presets** with carefully selected font pairings using the Mona Sans font family. + +## 🛠️ For Developers + + +### Project Structure +``` +ollie/ +├── patterns/ # 50+ block patterns +├── parts/ # Template parts (headers, footers) +├── templates/ # Page templates +├── styles/ # Style variations and presets +│ ├── blocks/ # Block-specific styles +│ ├── colors/ # Color palette variations +│ └── typography/ # Typography presets +├── theme.json # Global styles and settings +└── functions.php # Theme setup and configuration +``` + +### Theme Features +- **Translation Ready**: Full internationalization support +- **Pattern Translation**: Automated pattern text extraction +- **Child Theme Support**: Extend without modifying core +- **Modern PHP**: Clean, well-documented code +- **No Build Required**: Works out of the box + +### Customization Tips + +1. **Create a Child Theme**: Best practice for customizations +2. **Use Global Styles**: Modify colors, typography, and spacing in the Site Editor +3. **Extend Patterns**: Copy and modify existing patterns for your needs +4. **Custom CSS**: Add via Additional CSS in the Customizer or theme.json + +## 📚 Working with Full Site Editing + +New to Full Site Editing? We've got you covered! Check out our [YouTube channel](https://www.youtube.com/@OllieWP) for helpful tutorials on block themes, site editing, and getting the most out of Ollie. + +### Site Editor +Access the visual site builder at **Appearance → Editor** to: +- Edit headers, footers, and templates +- Customize global styles +- Create custom templates +- Build with patterns + +### Creating Pages with Patterns +1. Create a new page +2. Insert a full-page pattern from the Ollie collection +3. Apply the "No Title" template for full-width layouts +4. Customize content and publish + +### Global Styles +Powered by `theme.json`, customize: +- Color palettes +- Typography scales +- Spacing and layout +- Block defaults + +### Exporting Your Design +Share your customizations by exporting from the Site Editor: +**Options menu → Tools → Export** + +## 🧪 Development + +### Build Tools +Ollie includes modern development tools configured in `package.json`: + +```bash +# Watch for pattern changes and auto-escape for translations +npm run dev + +# Prepare patterns for translation +npm run translate:patterns +``` + +### Code Quality +Ollie includes Composer scripts for maintaining code standards: + +```bash +# Check PHP syntax +composer run lint + +# Scan for WordPress coding standards +composer run wpcs:scan + +# Auto-fix coding standard issues +composer run wpcs:fix +``` + +## 🚀 Ollie Pro + +Take your website to the next level with [Ollie Pro](https://olliewp.com/pro/): + +- **Setup Wizard**: Get started quickly with guided setup +- **One-Click Starter Sites**: Import complete website designs instantly +- **Premium Pattern Library**: Access exclusive pro patterns +- **Priority Support**: Get help when you need it +- **Regular Updates**: New patterns and features added regularly + +[Get Ollie Pro →](https://olliewp.com/pro/) + +## 📄 License + +Ollie is licensed under the [GPL-3.0 license](https://www.gnu.org/licenses/gpl-3.0.html). + +## 🤝 Community & Support + +- **Documentation**: [olliewp.com/docs](https://olliewp.com/docs) +- **Support Forum**: [wordpress.org/support/theme/ollie](https://wordpress.org/support/theme/ollie) +- **Bug Reports**: [GitHub Issues](https://github.com/OllieWP/ollie/issues) +- **Feature Requests**: [GitHub Discussions](https://github.com/OllieWP/ollie/discussions) + +## 👨‍💻 About + +Ollie is created and maintained by [Mike McAlister](https://mikemcalister.com) and [Patrick Posner](https://patrickposner.com). + +### Mike McAlister +- 🌐 [Website](https://mikemcalister.com) +- 🐦 [Twitter](https://twitter.com/mikemcalister) + +### Patrick Posner +- 🌐 [Website](https://patrickposner.com) +- 🐦 [Twitter](https://x.com/patrickposner_) + +### Ollie Resources +- 📺 [YouTube Tutorials](https://www.youtube.com/@OllieWP) +- ✍️ [Blog](https://olliewp.com) + +--- + +**[Download Ollie](https://github.com/OllieWP/ollie/releases/latest/download/ollie.zip)** | **[View Demo](https://demo.olliewp.com)** | **[Get Ollie Pro](https://olliewp.com)** diff --git a/wp-content/themes/ollie/assets/fonts/big-shoulders/BigShoulders-VariableFont_opsz,wght.woff2 b/wp-content/themes/ollie/assets/fonts/big-shoulders/BigShoulders-VariableFont_opsz,wght.woff2 new file mode 100644 index 0000000..c5d48ab Binary files /dev/null and b/wp-content/themes/ollie/assets/fonts/big-shoulders/BigShoulders-VariableFont_opsz,wght.woff2 differ diff --git a/wp-content/themes/ollie/assets/fonts/dm-sans/DMSans-VariableFont_opsz,wght.woff2 b/wp-content/themes/ollie/assets/fonts/dm-sans/DMSans-VariableFont_opsz,wght.woff2 new file mode 100644 index 0000000..e83f2c5 Binary files /dev/null and b/wp-content/themes/ollie/assets/fonts/dm-sans/DMSans-VariableFont_opsz,wght.woff2 differ diff --git a/wp-content/themes/ollie/assets/fonts/fraunces/Fraunces-VariableFont_SOFT,WONK,opsz,wght.woff2 b/wp-content/themes/ollie/assets/fonts/fraunces/Fraunces-VariableFont_SOFT,WONK,opsz,wght.woff2 new file mode 100644 index 0000000..f485dc4 Binary files /dev/null and b/wp-content/themes/ollie/assets/fonts/fraunces/Fraunces-VariableFont_SOFT,WONK,opsz,wght.woff2 differ diff --git a/wp-content/themes/ollie/assets/fonts/mona-sans/Mona-Sans.woff2 b/wp-content/themes/ollie/assets/fonts/mona-sans/Mona-Sans.woff2 new file mode 100644 index 0000000..876315b Binary files /dev/null and b/wp-content/themes/ollie/assets/fonts/mona-sans/Mona-Sans.woff2 differ diff --git a/wp-content/themes/ollie/assets/fonts/montagu-slab/MontaguSlab-VariableFont_opsz,wght.woff2 b/wp-content/themes/ollie/assets/fonts/montagu-slab/MontaguSlab-VariableFont_opsz,wght.woff2 new file mode 100644 index 0000000..0a3f75d Binary files /dev/null and b/wp-content/themes/ollie/assets/fonts/montagu-slab/MontaguSlab-VariableFont_opsz,wght.woff2 differ diff --git a/wp-content/themes/ollie/assets/fonts/source-serif/SourceSerif4-VariableFont_opsz,wght.woff2 b/wp-content/themes/ollie/assets/fonts/source-serif/SourceSerif4-VariableFont_opsz,wght.woff2 new file mode 100644 index 0000000..73460fd Binary files /dev/null and b/wp-content/themes/ollie/assets/fonts/source-serif/SourceSerif4-VariableFont_opsz,wght.woff2 differ diff --git a/wp-content/themes/ollie/assets/fonts/space-grotesk/SpaceGrotesk-VariableFont_wght.woff2 b/wp-content/themes/ollie/assets/fonts/space-grotesk/SpaceGrotesk-VariableFont_wght.woff2 new file mode 100644 index 0000000..126f9a3 Binary files /dev/null and b/wp-content/themes/ollie/assets/fonts/space-grotesk/SpaceGrotesk-VariableFont_wght.woff2 differ diff --git a/wp-content/themes/ollie/assets/styles/core-button.css b/wp-content/themes/ollie/assets/styles/core-button.css new file mode 100644 index 0000000..9e96aaf --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-button.css @@ -0,0 +1,14 @@ +/* Button - Outline Style +--------------------------------------------- */ + +.wp-block-button.is-style-outline .wp-block-button__link { + border: none; + background-color: transparent; + outline: 2px solid currentColor; + outline-offset: -3.5px; +} + +.wp-block-button.is-style-outline .wp-block-button__link:hover { + color: var(--wp--preset--color--main) !important; + outline-color: var(--wp--preset--color--main); +} diff --git a/wp-content/themes/ollie/assets/styles/core-calendar.css b/wp-content/themes/ollie/assets/styles/core-calendar.css new file mode 100644 index 0000000..a8d12f3 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-calendar.css @@ -0,0 +1,28 @@ +/* Calendar +--------------------------------------------- */ + +.wp-block-calendar table caption { + font-weight: var(--wp--custom--font-weight--bold); + margin-bottom: var(--wp--preset--spacing--small); +} + +.wp-block-calendar table caption, +.wp-block-calendar table tbody { + color: var(--wp--preset--color--main); +} + +.wp-block-calendar table th { + background-color: var(--wp--preset--color--tertiary); + color: var(--wp--preset--color--main); + font-weight: var(--wp--custom--font-weight--bold); +} + +.wp-block-calendar tbody td, +.wp-block-calendar th { + border: 1px solid var(--wp--preset--color--tertiary); + padding: 10px; +} + +.wp-block-calendar nav { + margin-top: var(--wp--preset--spacing--small); +} diff --git a/wp-content/themes/ollie/assets/styles/core-code.css b/wp-content/themes/ollie/assets/styles/core-code.css new file mode 100644 index 0000000..c7a4958 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-code.css @@ -0,0 +1,22 @@ +/* Code +--------------------------------------------- */ + +.wp-block-code code { + overflow-wrap: normal; + overflow-x: scroll; + white-space: pre; +} + +*:not(.wp-block-code) > code { + background-color: var(--wp--preset--color--tertiary); + font-weight: var(--wp--custom--font-weight--medium);; + padding: 3px 8px; + position: relative; + border-radius: 3px; +} + +.is-style-dark-code, +.editor-styles-wrapper .wp-block-code.is-style-dark-code { + background-color: var(--wp--preset--color--main); + color: var(--wp--preset--color--base); +} diff --git a/wp-content/themes/ollie/assets/styles/core-columns.css b/wp-content/themes/ollie/assets/styles/core-columns.css new file mode 100644 index 0000000..0bbd173 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-columns.css @@ -0,0 +1,21 @@ +/* Column +--------------------------------------------- */ + +.is-style-column-box-shadow { + box-shadow: 0px 8px 40px -20px rgb(21 14 41 / 12%); + transition: .4s ease; +} + +.is-style-column-box-shadow:hover { + box-shadow: 0px 12px 60px -20px rgb(21 14 41 / 16%); +} + +/* Helper class to swap order on mobile */ +@media (max-width: 781px) { + .ollie-swap-order { + flex-direction: column-reverse; + } + .ollie-row-reverse { + flex-direction: row-reverse; + } +} diff --git a/wp-content/themes/ollie/assets/styles/core-cover.css b/wp-content/themes/ollie/assets/styles/core-cover.css new file mode 100644 index 0000000..4c96413 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-cover.css @@ -0,0 +1,30 @@ +/* Cover styles +--------------------------------------------- */ + +.is-style-blur-image-less, +.is-style-blur-image-more { + overflow: hidden; +} + +.is-style-blur-image-less > .wp-block-cover__image-background, +.is-style-blur-image-more > .wp-block-cover__image-background { + transform: scale(1.5); +} + +.is-style-blur-image-less > .wp-block-cover__image-background { + filter: blur(25px); +} + +.is-style-blur-image-more > .wp-block-cover__image-background { + filter: blur(75px); +} + +.is-style-rounded-cover img { + border-radius: 5px; +} + +@media (max-width: 781px) { + .wp-block-cover:not(.has-aspect-ratio) { + min-height: 430px !important; + } +} diff --git a/wp-content/themes/ollie/assets/styles/core-gallery.css b/wp-content/themes/ollie/assets/styles/core-gallery.css new file mode 100644 index 0000000..775744c --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-gallery.css @@ -0,0 +1,10 @@ +/* Caption +--------------------------------------------- */ + +.wp-block-gallery figcaption.blocks-gallery-caption { + margin-top: 0; +} + +.ollie-avatar-row > figure:not(:first-child) { + margin-left: -10px !important; +} diff --git a/wp-content/themes/ollie/assets/styles/core-group.css b/wp-content/themes/ollie/assets/styles/core-group.css new file mode 100644 index 0000000..3f1dd88 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-group.css @@ -0,0 +1,46 @@ +/* Group +--------------------------------------------- */ + +.is-style-background-blur { + -webkit-backdrop-filter: blur(20px); + backdrop-filter: blur(20px); +} + +/* Improvements for row group */ +@media (max-width: 781px) { + .ollie-row-stack { + flex-direction: column; + align-items: flex-start !important; + } + + .ollie-row-stack > * { + flex-basis: 100% !important; + } + + .ollie-flex-start { + align-items: flex-start !important; + } + + .ollie-justify-start { + justify-content: flex-start !important; + } +} + +.ollie-row-stack > .wp-block-buttons, +.ollie-no-shrink { + flex-shrink: 0; +} + +.wp-block-group.ollie-sticky-top { + top: calc(20px + var(--wp-admin--admin-bar--position-offset, 0px)) +} + +/* Helper class to swap order on mobile */ +@media (max-width: 781px) { + .ollie-swap-order { + flex-direction: column-reverse; + } + .ollie-row-reverse { + flex-direction: row-reverse; + } +} diff --git a/wp-content/themes/ollie/assets/styles/core-image.css b/wp-content/themes/ollie/assets/styles/core-image.css new file mode 100644 index 0000000..212e485 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-image.css @@ -0,0 +1,56 @@ +/* Image +--------------------------------------------- */ + +.wp-block-image.alignleft { + margin-bottom: var(--wp--preset--spacing--small); +} + +.wp-block-image.alignright { + margin-bottom: var(--wp--preset--spacing--small); +} + +@media only screen and (max-width: 600px) { + .is-layout-flow .wp-block-image.alignleft, + .is-layout-flow .wp-block-image.alignright { + float: none; + margin-left: auto; + margin-right: auto; + } +} + +html .is-layout-flex .wp-block-image { + flex-shrink: 0; +} + +.wp-block-image.is-style-rounded img, +.wp-block-image .is-style-rounded img { + border-radius: 10px; +} + +.wp-block-image.is-style-rounded-full img, +.wp-block-image .is-style-rounded-full img { + border-radius: 1000px; +} + +.is-style-media-boxed { + background-color: var(--wp--preset--color--tertiary); + padding: var(--wp--preset--spacing--large); + border-radius: 5px; +} + +.is-style-media-boxed img { + box-shadow: + 1px 2px 2px hsl(233deg 38% 85% / 0.2), + 2px 4px 4px hsl(233deg 38% 85% / 0.2), + 4px 8px 8px hsl(233deg 38% 85% / 0.2), + 8px 16px 16px hsl(233deg 38% 85% / 0.2), + 16px 32px 32px hsl(233deg 38% 85% / 0.2); +} + +.is-style-media-boxed figcaption { + margin-bottom: calc(var(--wp--preset--spacing--small) * -1) !important; +} + +.ollie-avatar-row > figure:not(:first-child) { + margin-left: -10px !important; +} diff --git a/wp-content/themes/ollie/assets/styles/core-list.css b/wp-content/themes/ollie/assets/styles/core-list.css new file mode 100644 index 0000000..57291e5 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-list.css @@ -0,0 +1,95 @@ +/* List +--------------------------------------------- */ + +/* List styles for default unordered lists */ +.entry-content ul li, +.entry-content ol li, +.wp-block-post-content ul li, +.wp-block-post-content ol li { + margin-bottom: var(--wp--preset--spacing--small); +} + +.entry-content ul ul, +.entry-content ol ul, +.wp-block-post-content ul ul, +.wp-block-post-content ol ul { + margin-top: var(--wp--preset--spacing--small); +} + +/* Remove paddings */ +.entry-content :not(.wp-block-group) > li:first-child { + padding-top: 0; +} + +.entry-content :not(.wp-block-group) li:last-child { + padding-bottom: 0; +} + +ul.is-style-list-check, +ul.is-style-list-check ul, +ul.is-style-list-check-circle, +ul.is-style-list-check-circle ul { + padding-inline-start: 0px !important; + padding-left: 0; + list-style: none; +} + +.entry-content ul.is-style-list-check, +.entry-content ul.is-style-list-check-circle { + padding-inline-start: .5rem; +} + +ul.is-style-list-check li, +ul.is-style-list-check-circle li { + position: relative; + padding-left: calc(var(--wp--preset--spacing--medium) + .5rem); +} + +ul.is-style-list-check li:before, +ul.is-style-list-check-circle li:before { + content: "\2713"; + position: absolute; + left: 0; + top: .1em; +} + +ul.is-style-list-check-circle li:before { + background: var(--wp--preset--color--main); + color: var(--wp--preset--color--base); + border-radius: 100px; + height: 1.5rem; + width: 1.5rem; + line-height: 1.5rem; + text-align: center; + font-size: var(--wp--preset--font-size--base); + transform: scale(.8); +} + +ul.is-style-list-check-circle li { + padding-left: calc(var(--wp--preset--spacing--medium) + .5rem); +} + +@media (max-width: 781px) { + ul.is-style-list-check-circle li:before { + top: .05em; + } + + ul.is-style-list-check-circle li { + padding-left: calc(var(--wp--preset--spacing--medium) + .8rem); + } +} + +/* Boxed list style */ +ul.is-style-list-boxed, +ol.is-style-list-boxed, +ul.is-style-list-boxed.wp-block, +ol.is-style-list-boxed.wp-block { + background: var(--wp--preset--color--tertiary); + padding: var(--wp--preset--spacing--medium) var(--wp--preset--spacing--large); + border-radius: 5px; +} + +ul.is-style-list-boxed li:last-child, +ol.is-style-list-boxed li:last-child { + margin-bottom: 0; +} diff --git a/wp-content/themes/ollie/assets/styles/core-navigation.css b/wp-content/themes/ollie/assets/styles/core-navigation.css new file mode 100644 index 0000000..bcf709b --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-navigation.css @@ -0,0 +1,162 @@ +/* Desktop Navigation +--------------------------------------------- */ + +.wp-block-navigation__responsive-container:not(.is-menu-open) .wp-block-navigation__container .current-menu-item > a, +.wp-block-navigation__responsive-container:not(.is-menu-open) .wp-block-navigation__container .has-child button:hover { + text-decoration: underline; +} + +/* Drop nav */ +.wp-block-navigation__responsive-container:not(.is-menu-open) .wp-block-navigation__submenu-container { + border: none !important; + font-size: var(--wp--preset--font-size--small); + line-height: var(--wp--custom--line-height--snug); + border-radius: 5px; + min-width: 225px !important; + margin-left: calc(var(--wp--preset--spacing--medium) * -1); + padding: 0; + z-index: 20 !important; + border-radius: 5px; + box-shadow: var(--wp--preset--shadow--small-light); +} + +/* Drop nav submenu */ +.wp-block-navigation__responsive-container:not(.is-menu-open) .wp-block-navigation__submenu-container .wp-block-navigation__submenu-container { + margin-left: 0; + top: 0 !important; + left: 100%; +} + +.wp-block-navigation__responsive-container:not(.is-menu-open) :where(.wp-block-navigation__submenu-container) li:first-child { + padding-top: var(--wp--preset--spacing--small); + border-top-left-radius: 5px; + border-top-right-radius: 5px; +} + +/* Add padding to bottom of drop menu */ +.wp-block-navigation__responsive-container:not(.is-menu-open) :where(.wp-block-navigation__submenu-container) li:last-child { + padding-bottom: var(--wp--preset--spacing--small); + border-bottom-left-radius: 5px; + border-bottom-right-radius: 5px; +} + +/* Add margin to sub menu icon */ +.wp-block-navigation__responsive-container:not(.is-menu-open) .has-child .wp-block-navigation__submenu-container .wp-block-navigation__submenu-icon { + margin-right: var(--wp--preset--spacing--small); +} + +/* Drop nav link padding */ +.wp-block-navigation__responsive-container:not(.is-menu-open) :where(.wp-block-navigation__submenu-container) a, +.wp-block-navigation__responsive-container:not(.is-menu-open) :where(.wp-block-navigation__submenu-container) .wp-block-navigation-submenu__toggle { + padding: var(--wp--preset--spacing--small) var(--wp--preset--spacing--medium) !important; +} + +/* Mobile Navigation +--------------------------------------------- */ + +.wp-block-navigation__responsive-container.is-menu-open.has-modal-open { + padding: var(--wp--preset--spacing--medium); +} + +.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content { + gap: var(--wp--preset--spacing--medium); +} + +.wp-block-navigation__responsive-container.is-menu-open.has-modal-open .wp-block-navigation__container { + width: 100%; + gap: 5px !important; +} + +.wp-block-navigation__responsive-container.is-menu-open.has-modal-open .wp-block-page-list { + width: 100%; +} + +.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .has-child .wp-block-navigation__submenu-container { + border-radius: 0; + margin: 0; + padding: 5px 0 0 20px; + align-items: flex-start; + flex-direction: column; + gap: 5px; + width: 100%; +} + +.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__submenu-icon { + display: block; + position: absolute; + right: 0; + top: 5px; + height: auto; + width: 60px; + padding: 15px 0; + margin: 0; + z-index: 10; +} + +body.rtl .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__submenu-icon { + left: 0; + right: auto; +} + +.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation__submenu-icon svg { + height: 16px; + margin: 0; +} + +.wp-block-navigation .has-child .wp-block-navigation__submenu-container { + display: none; +} + +.wp-block-navigation-submenu__toggle[aria-expanded="true"] ~ .wp-block-navigation-submenu, +.wp-block-navigation-submenu__toggle[aria-expanded="true"] ~ .wp-block-navigation__submenu-container { + display: flex; +} + +.wp-block-navigation__responsive-container.is-menu-open.has-modal-open .wp-block-navigation__container li:not(.wp-social-link) { + width: 100%; + padding: 0 0; + position: relative; +} + +/* Mobile menu links */ +.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation-item__content { + width: 100%; + font-size: var(--wp--preset--font-size--base); + padding: 15px 60px 15px 15px; + border-radius: 5px; +} + +body.rtl .wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__responsive-container-content .wp-block-navigation-item__content { + padding: 15px 15px 15px 60px; +} + +.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container .current-menu-item > .wp-block-navigation-item__content, +.wp-block-navigation__responsive-container.is-menu-open .wp-block-navigation__container .wp-block-navigation-item:hover > .wp-block-navigation-item__content { + transition: .3s ease; + text-decoration: none; + background: color-mix(in srgb, currentColor, transparent 97%); + -webkit-tap-highlight-color: transparent; +} + +.wp-block-navigation__container .wp-block-navigation-item:has(.wp-block-navigation__submenu-container:hover) > .wp-block-navigation-item__content { + background: transparent; +} + +/* Mobile menu open button */ +.wp-block-navigation__responsive-container-close, +.wp-block-navigation__responsive-container-open { + padding: 4px; + border-radius: 3px; + background: var(--wp--preset--color--tertiary); + color: var(--wp--preset--color--main); +} + +/* Mobile menu close button */ +.wp-block-navigation__responsive-container-close { + background: var(--wp--preset--color--tertiary); + color: var(--wp--preset--color--main); +} + +.wp-block-ollie-mega-menu__menu-container { + font-weight: 400; +} diff --git a/wp-content/themes/ollie/assets/styles/core-post-author.css b/wp-content/themes/ollie/assets/styles/core-post-author.css new file mode 100644 index 0000000..b0785a1 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-post-author.css @@ -0,0 +1,22 @@ +/* Post Author +--------------------------------------------- */ + +.wp-block-post-author { + align-items: center; +} + +.wp-block-post-author__name { + margin-bottom: 0; +} + +.wp-block-post-author__avatar { + display: inline-flex; + align-items: center; + margin-right: .8rem; +} + +.wp-block-post-author__avatar img { + width: 26px; + height: 26px; + border-radius: 100px; +} diff --git a/wp-content/themes/ollie/assets/styles/core-post-excerpt.css b/wp-content/themes/ollie/assets/styles/core-post-excerpt.css new file mode 100644 index 0000000..516745c --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-post-excerpt.css @@ -0,0 +1,28 @@ +/* Post Excerpt +--------------------------------------------- */ + +ul[class*="columns-"].wp-block-post-template .wp-block-post-excerpt__more-text { + margin-top: var(--wp--preset--spacing--small); +} + +div[class*="is-style-excerpt-truncate-"], +div[class*="is-style-excerpt-truncate-"] .wp-block-post-excerpt__excerpt { + display: -webkit-box; + -webkit-box-orient: vertical; + overflow: hidden; +} + +.is-style-excerpt-truncate-2, +.is-style-excerpt-truncate-2 .wp-block-post-excerpt__excerpt { + -webkit-line-clamp: 2; +} + +.is-style-excerpt-truncate-3, +.is-style-excerpt-truncate-3 .wp-block-post-excerpt__excerpt { + -webkit-line-clamp: 3; +} + +.is-style-excerpt-truncate-4, +.is-style-excerpt-truncate-4 .wp-block-post-excerpt__excerpt { + -webkit-line-clamp: 4; +} diff --git a/wp-content/themes/ollie/assets/styles/core-post-template.css b/wp-content/themes/ollie/assets/styles/core-post-template.css new file mode 100644 index 0000000..76a93d7 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-post-template.css @@ -0,0 +1,11 @@ +/* Post Template +--------------------------------------------- */ + +ul[class*="columns-"].wp-block-post-template .wp-block-post > .wp-block-group { + height: 100%; +} + +/* Hide empty pagination container */ +.wp-block-post-template + .wp-block-group:empty { + display: none; +} diff --git a/wp-content/themes/ollie/assets/styles/core-post-terms.css b/wp-content/themes/ollie/assets/styles/core-post-terms.css new file mode 100644 index 0000000..3365482 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-post-terms.css @@ -0,0 +1,25 @@ +/* Categories +--------------------------------------------- */ + +.is-style-term-button a { + padding: 6px 12px; + border-radius: 5px; + background-color: var(--wp--preset--color--tertiary); + font-size: var(--wp--preset--font-size--x-small); + color: var(--wp--preset--color--primary); + line-height: 1.4; +} + +.single .taxonomy-post_tag.is-style-term-button { + display: flex; + gap: var(--wp--preset--spacing--small); + flex-wrap: wrap; +} + +.single .taxonomy-post_tag.is-style-term-button .wp-block-post-terms__separator { + display: none; +} + +.single .post-meta:empty { + display: none; +} diff --git a/wp-content/themes/ollie/assets/styles/core-preformatted.css b/wp-content/themes/ollie/assets/styles/core-preformatted.css new file mode 100644 index 0000000..832635c --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-preformatted.css @@ -0,0 +1,14 @@ +/* Preformatted +--------------------------------------------- */ + +.wp-block-preformatted { + overflow-wrap: normal; + overflow-x: scroll; + white-space: pre; +} + +.wp-block-preformatted.is-style-preformatted-dark, +.editor-styles-wrapper .wp-block-preformatted.is-style-preformatted-dark { + background-color: var(--wp--preset--color--main); + color: var(--wp--preset--color--base); +} diff --git a/wp-content/themes/ollie/assets/styles/core-pullquote.css b/wp-content/themes/ollie/assets/styles/core-pullquote.css new file mode 100644 index 0000000..ae99571 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-pullquote.css @@ -0,0 +1,28 @@ +/* Pullquote +--------------------------------------------- */ + +.wp-block-pullquote blockquote { + margin: 0; +} + +.wp-block-pullquote.alignleft, +.wp-block-pullquote.alignright { + padding-bottom: var(--wp--preset--spacing--medium); +} + +.wp-block-pullquote p { + margin-block-start: var(--wp--preset--spacing--medium); +} + +.wp-block-pullquote cite { + display: block; +} + +@media only screen and (max-width: 781px) { + .wp-block-pullquote.alignright, + .wp-block-pullquote.alignleft { + max-width: 100%; + margin-left: 0 !important; + margin-right: 0 !important; + } +} diff --git a/wp-content/themes/ollie/assets/styles/core-query-pagination-numbers.css b/wp-content/themes/ollie/assets/styles/core-query-pagination-numbers.css new file mode 100644 index 0000000..f891d6e --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-query-pagination-numbers.css @@ -0,0 +1,17 @@ +/* Query pagination +--------------------------------------------- */ + +.wp-block-query-pagination-previous, +.wp-block-query-pagination-next, +.wp-block-query-pagination-numbers { + margin: 0 !important; +} + +.wp-block-query-pagination-numbers a { + display: inline-block; +} + +.wp-block-query-pagination-numbers span.page-numbers { + padding-left: .7em; + padding-right: .7em; +} diff --git a/wp-content/themes/ollie/assets/styles/core-separator.css b/wp-content/themes/ollie/assets/styles/core-separator.css new file mode 100644 index 0000000..7532fbd --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-separator.css @@ -0,0 +1,34 @@ +/* Separator +--------------------------------------------- */ + +.wp-block-separator { + opacity: 1; +} + +.wp-block-separator:not(.is-style-dots), +.wp-block-separator.has-background:not(.is-style-dots) { + border-bottom: 1px solid currentColor; + height: 1px; +} + +.wp-block-separator.is-style-dots::before { + font-family: sans-serif; + font-size: var(--wp--preset--font-size--large); + letter-spacing: 10px; + padding-left: 10px; +} + +hr.is-style-separator-dotted, +.editor-styles-wrapper hr.is-style-separator-dotted { + width: 100% !important; + height: 1px !important; + border: none !important; + height: 1px !important; + background-color: none !important; + background: currentColor !important; + background: repeating-linear-gradient(90deg,currentColor,currentColor 2px,transparent 2px,transparent 5px) !important; +} + +.is-style-separator-thin { + border-top: 1px !important; +} diff --git a/wp-content/themes/ollie/assets/styles/core-table.css b/wp-content/themes/ollie/assets/styles/core-table.css new file mode 100644 index 0000000..832bb3c --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-table.css @@ -0,0 +1,6 @@ +/* Table +--------------------------------------------- */ + +.wp-block-table.is-style-stripes tbody tr:nth-child(odd) { + background-color: var(--wp--preset--color--tertiary); +} diff --git a/wp-content/themes/ollie/assets/styles/core-video.css b/wp-content/themes/ollie/assets/styles/core-video.css new file mode 100644 index 0000000..3300bee --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/core-video.css @@ -0,0 +1,22 @@ +/* Video +--------------------------------------------- */ + +.is-style-media-boxed { + background-color: var(--wp--preset--color--tertiary); + padding: var(--wp--preset--spacing--large); + border-radius: 5px; +} + +.is-style-media-boxed video { + display: block; + box-shadow: + 1px 2px 2px hsl(233deg 38% 85% / 0.2), + 2px 4px 4px hsl(233deg 38% 85% / 0.2), + 4px 8px 8px hsl(233deg 38% 85% / 0.2), + 8px 16px 16px hsl(233deg 38% 85% / 0.2), + 16px 32px 32px hsl(233deg 38% 85% / 0.2); +} + +.is-style-media-boxed figcaption { + margin-bottom: calc(var(--wp--preset--spacing--small) * -1) !important; +} diff --git a/wp-content/themes/ollie/assets/styles/woocommerce.css b/wp-content/themes/ollie/assets/styles/woocommerce.css new file mode 100644 index 0000000..1dff719 --- /dev/null +++ b/wp-content/themes/ollie/assets/styles/woocommerce.css @@ -0,0 +1,18 @@ +/* WooCommerce styles +--------------------------------------------- */ + +.woocommerce div.product form.cart .variations select { + background-color: #fff; + height: auto; + padding: .5em 2em .5em 1em; +} + +.woocommerce div.product form.cart .variations label { + margin: 0; +} + +.wp-block-woocommerce-add-to-cart-form .variations_button>.quantity:not(.wc-block-components-quantity-selector) .qty { + height: auto; + padding: .6em; + width: 4em; +} diff --git a/wp-content/themes/ollie/functions.php b/wp-content/themes/ollie/functions.php new file mode 100644 index 0000000..ff10698 --- /dev/null +++ b/wp-content/themes/ollie/functions.php @@ -0,0 +1,214 @@ +get( 'Version' ) ); +} +add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_style_sheet' ); + + +/** + * Add block style variations. + */ +function register_block_styles() { + + $block_styles = array( + 'core/list' => array( + 'list-check' => __( 'Check', 'ollie' ), + 'list-check-circle' => __( 'Check Circle', 'ollie' ), + 'list-boxed' => __( 'Boxed', 'ollie' ), + ), + 'core/code' => array( + 'dark-code' => __( 'Dark', 'ollie' ), + ), + 'core/cover' => array( + 'blur-image-less' => __( 'Blur Image Less', 'ollie' ), + 'blur-image-more' => __( 'Blur Image More', 'ollie' ), + 'rounded-cover' => __( 'Rounded', 'ollie' ), + ), + 'core/column' => array( + 'column-box-shadow' => __( 'Box Shadow', 'ollie' ), + ), + 'core/post-excerpt' => array( + 'excerpt-truncate-2' => __( 'Truncate 2 Lines', 'ollie' ), + 'excerpt-truncate-3' => __( 'Truncate 3 Lines', 'ollie' ), + 'excerpt-truncate-4' => __( 'Truncate 4 Lines', 'ollie' ), + ), + 'core/group' => array( + 'column-box-shadow' => __( 'Box Shadow', 'ollie' ), + 'background-blur' => __( 'Background Blur', 'ollie' ), + ), + 'core/separator' => array( + 'separator-dotted' => __( 'Dotted', 'ollie' ), + 'separator-thin' => __( 'Thin', 'ollie' ), + ), + 'core/image' => array( + 'rounded-full' => __( 'Rounded Full', 'ollie' ), + 'media-boxed' => __( 'Boxed', 'ollie' ), + ), + 'core/preformatted' => array( + 'preformatted-dark' => __( 'Dark Style', 'ollie' ), + ), + 'core/post-terms' => array( + 'term-button' => __( 'Button Style', 'ollie' ), + ), + 'core/video' => array( + 'media-boxed' => __( 'Boxed', 'ollie' ), + ), + ); + + foreach ( $block_styles as $block => $styles ) { + foreach ( $styles as $style_name => $style_label ) { + register_block_style( + $block, + array( + 'name' => $style_name, + 'label' => $style_label, + ) + ); + } + } +} +add_action( 'init', __NAMESPACE__ . '\register_block_styles' ); + + +/** + * Load custom block styles only when the block is used. + */ +function enqueue_custom_block_styles() { + + // Scan our styles folder to locate block styles. + $files = glob( get_template_directory() . '/assets/styles/*.css' ); + + foreach ( $files as $file ) { + + // Get the filename and core block name. + $filename = basename( $file, '.css' ); + $block_name = str_replace( 'core-', 'core/', $filename ); + + wp_enqueue_block_style( + $block_name, + array( + 'handle' => "ollie-block-{$filename}", + 'src' => get_theme_file_uri( "assets/styles/{$filename}.css" ), + 'path' => get_theme_file_path( "assets/styles/{$filename}.css" ), + ) + ); + } +} +add_action( 'init', __NAMESPACE__ . '\enqueue_custom_block_styles' ); + + +/** + * Enqueue WooCommerce specific stylesheet + */ +function enqueue_woocommerce_styles() { + + // Only enqueue if WooCommerce is active + if ( class_exists( 'WooCommerce' ) ) { + wp_enqueue_style( + 'theme-woocommerce-style', + get_template_directory_uri() . '/assets/styles/woocommerce.css', + array(), + '1.0.0' + ); + } +} +add_action( 'wp_enqueue_scripts', __NAMESPACE__ . '\enqueue_woocommerce_styles' ); + + +/** + * Register pattern categories. + */ +function pattern_categories() { + + $block_pattern_categories = array( + 'ollie/card' => array( + 'label' => __( 'Cards', 'ollie' ), + ), + 'ollie/call-to-action' => array( + 'label' => __( 'Call To Action', 'ollie' ), + ), + 'ollie/features' => array( + 'label' => __( 'Features', 'ollie' ), + ), + 'ollie/hero' => array( + 'label' => __( 'Hero', 'ollie' ), + ), + 'ollie/pages' => array( + 'label' => __( 'Pages', 'ollie' ), + ), + 'ollie/posts' => array( + 'label' => __( 'Posts', 'ollie' ), + ), + 'ollie/pricing' => array( + 'label' => __( 'Pricing', 'ollie' ), + ), + 'ollie/testimonial' => array( + 'label' => __( 'Testimonials', 'ollie' ), + ), + 'ollie/menu' => array( + 'label' => __( 'Menu', 'ollie' ), + ) + ); + + foreach ( $block_pattern_categories as $name => $properties ) { + register_block_pattern_category( $name, $properties ); + } +} +add_action( 'init', __NAMESPACE__ . '\pattern_categories', 9 ); + + +/** + * Remove last separator on blog/archive if no pagination exists. + */ +function is_paginated() { + global $wp_query; + if ( $wp_query->max_num_pages < 2 ) { + echo ''; + } +} +add_action( 'wp_head', __NAMESPACE__ . '\is_paginated' ); + + +/** + * Add a Sidebar template part area + */ +function template_part_areas( array $areas ) { + $areas[] = array( + 'area' => 'sidebar', + 'area_tag' => 'section', + 'label' => __( 'Sidebar', 'ollie' ), + 'description' => __( 'The Sidebar template defines a page area that can be found on the Page (With Sidebar) template.', 'ollie' ), + 'icon' => 'sidebar', + ); + + return $areas; +} +add_filter( 'default_wp_template_part_areas', __NAMESPACE__ . '\template_part_areas' ); diff --git a/wp-content/themes/ollie/index.php b/wp-content/themes/ollie/index.php new file mode 100644 index 0000000..bc6de57 --- /dev/null +++ b/wp-content/themes/ollie/index.php @@ -0,0 +1,9 @@ + diff --git a/wp-content/themes/ollie/parts/header.html b/wp-content/themes/ollie/parts/header.html new file mode 100644 index 0000000..6cd0b8a --- /dev/null +++ b/wp-content/themes/ollie/parts/header.html @@ -0,0 +1 @@ + diff --git a/wp-content/themes/ollie/parts/sidebar.html b/wp-content/themes/ollie/parts/sidebar.html new file mode 100644 index 0000000..0e7fc45 --- /dev/null +++ b/wp-content/themes/ollie/parts/sidebar.html @@ -0,0 +1,13 @@ + +
+

Sidebar Template

+ + + +

Ollie comes with a sidebar template where you can easily add sidebar content to any of your pages.

+ + + +

You can modify the template part here, or you can find it in the Site Editor under Patterns → Sidebar.

+
+ diff --git a/wp-content/themes/ollie/patterns/author-box.php b/wp-content/themes/ollie/patterns/author-box.php new file mode 100644 index 0000000..b77ffeb --- /dev/null +++ b/wp-content/themes/ollie/patterns/author-box.php @@ -0,0 +1,34 @@ + + +
+
+ + +
+ + + + + +
+
+
+ diff --git a/wp-content/themes/ollie/patterns/blog-post-columns-single.php b/wp-content/themes/ollie/patterns/blog-post-columns-single.php new file mode 100644 index 0000000..f7209bb --- /dev/null +++ b/wp-content/themes/ollie/patterns/blog-post-columns-single.php @@ -0,0 +1,32 @@ + + +
+
+ +
+
+ + + +
+
+ +
+
+
+ +
+
+ diff --git a/wp-content/themes/ollie/patterns/blog-post-columns.php b/wp-content/themes/ollie/patterns/blog-post-columns.php new file mode 100644 index 0000000..11a7833 --- /dev/null +++ b/wp-content/themes/ollie/patterns/blog-post-columns.php @@ -0,0 +1,34 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+ +
+
+ diff --git a/wp-content/themes/ollie/patterns/card-big-text-call-to-action.php b/wp-content/themes/ollie/patterns/card-big-text-call-to-action.php new file mode 100644 index 0000000..a62c1d6 --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-big-text-call-to-action.php @@ -0,0 +1,34 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-blog-post.php b/wp-content/themes/ollie/patterns/card-blog-post.php new file mode 100644 index 0000000..68bead7 --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-blog-post.php @@ -0,0 +1,38 @@ + + +
+
+ + + + + + +
+ +
+
+ + + +
+
+ diff --git a/wp-content/themes/ollie/patterns/card-call-to-action-with-buttons.php b/wp-content/themes/ollie/patterns/card-call-to-action-with-buttons.php new file mode 100644 index 0000000..b85f74c --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-call-to-action-with-buttons.php @@ -0,0 +1,34 @@ + + +
+
+

+ + + +

+
+ + + +
+
+ + + +
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-call-to-action.php b/wp-content/themes/ollie/patterns/card-call-to-action.php new file mode 100644 index 0000000..fb8160a --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-call-to-action.php @@ -0,0 +1,40 @@ + + +
+
+

+ + + +

+
+ + + +
+

+ + + +
+
+
+ + + + +
+
+ diff --git a/wp-content/themes/ollie/patterns/card-contact.php b/wp-content/themes/ollie/patterns/card-contact.php new file mode 100644 index 0000000..0feb0bb --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-contact.php @@ -0,0 +1,74 @@ + + +
+
+

+ + + + +
+ + + +
+
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +


+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-details.php b/wp-content/themes/ollie/patterns/card-details.php new file mode 100644 index 0000000..0efd4da --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-details.php @@ -0,0 +1,74 @@ + + +
+
+

+ + + + +
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-image-and-text.php b/wp-content/themes/ollie/patterns/card-image-and-text.php new file mode 100644 index 0000000..b53bd71 --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-image-and-text.php @@ -0,0 +1,38 @@ + + +
+
+ + + +
+

+ + + +

+
+ + + +
+
+ + + +
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-lead-magnet.php b/wp-content/themes/ollie/patterns/card-lead-magnet.php new file mode 100644 index 0000000..076b7a2 --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-lead-magnet.php @@ -0,0 +1,44 @@ + + +
+
+
+
+

+
+
+ + + +
+

+ + + +

+ + + +

+ + + +
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-post-list.php b/wp-content/themes/ollie/patterns/card-post-list.php new file mode 100644 index 0000000..5c8d5da --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-post-list.php @@ -0,0 +1,50 @@ + + +
+
+

+ + + +

+
+ + + +
+
+
+ +
+ +
+ + + +
+ +
+ + + + +
+

+
+ +
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-pricing-table-dark.php b/wp-content/themes/ollie/patterns/card-pricing-table-dark.php new file mode 100644 index 0000000..f23c0f6 --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-pricing-table-dark.php @@ -0,0 +1,108 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/card-pricing-table.php b/wp-content/themes/ollie/patterns/card-pricing-table.php new file mode 100644 index 0000000..ae7a67c --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-pricing-table.php @@ -0,0 +1,94 @@ + + +
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-social-profile.php b/wp-content/themes/ollie/patterns/card-social-profile.php new file mode 100644 index 0000000..9d37364 --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-social-profile.php @@ -0,0 +1,46 @@ + + +
+
+
+ + + +
+

+ + + +

+
+
+ + + +
+

+ + + + +
+
+ diff --git a/wp-content/themes/ollie/patterns/card-testimonial.php b/wp-content/themes/ollie/patterns/card-testimonial.php new file mode 100644 index 0000000..abecd60 --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-testimonial.php @@ -0,0 +1,30 @@ + + +
+

+ + + +
+
+ + + +
+

+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-text-and-call-to-action.php b/wp-content/themes/ollie/patterns/card-text-and-call-to-action.php new file mode 100644 index 0000000..85925bb --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-text-and-call-to-action.php @@ -0,0 +1,84 @@ + + +
+
+
+
+

+ + + +

+ + + +

+
+ + + +
+ + + +
+
+

+ + + +

+ + + +

+
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+
+

+
+ + + +
+
+
+
+ + + + +
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-text-and-details.php b/wp-content/themes/ollie/patterns/card-text-and-details.php new file mode 100644 index 0000000..45e9f92 --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-text-and-details.php @@ -0,0 +1,128 @@ + + +
+
+
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + + +
+ + + +
+ + + +
+
+ + + +
+

+ + + + +
+
+
+ + + +
+
+
+

+ + + + +
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/card-text-box-with-link.php b/wp-content/themes/ollie/patterns/card-text-box-with-link.php new file mode 100644 index 0000000..8da586a --- /dev/null +++ b/wp-content/themes/ollie/patterns/card-text-box-with-link.php @@ -0,0 +1,34 @@ + + +
+
+

+ + + +

+
+ + + +
+
+ + + + +
+
+ diff --git a/wp-content/themes/ollie/patterns/comments.php b/wp-content/themes/ollie/patterns/comments.php new file mode 100644 index 0000000..20a30b4 --- /dev/null +++ b/wp-content/themes/ollie/patterns/comments.php @@ -0,0 +1,56 @@ + + +
+
+
+

+ + +
+ + + + +
+
+ + +
+ +
+
+ + + + + + +
+ + + + +
+ + + +
+ + +
+
+ diff --git a/wp-content/themes/ollie/patterns/contact-details.php b/wp-content/themes/ollie/patterns/contact-details.php new file mode 100644 index 0000000..2819162 --- /dev/null +++ b/wp-content/themes/ollie/patterns/contact-details.php @@ -0,0 +1,122 @@ + + +
+
+
+
+

+ + + +

+ + + +

+
+ + + +
+
+ + + +
+
+ + + +
+ + + +
+

+ + + +

+ + + +

+
+
+ + + +
+
+

+ + + + +
+ + + +
+
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +


+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/faq.php b/wp-content/themes/ollie/patterns/faq.php new file mode 100644 index 0000000..1da473a --- /dev/null +++ b/wp-content/themes/ollie/patterns/faq.php @@ -0,0 +1,112 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+
+

+ + + +

+
+
+ + + +
+
+

+ + + +

+
+
+
+ + + +
+
+
+

+ + + +

+
+
+ + + +
+
+

+ + + +

+
+
+
+
+
+ + + +
+
+
+
+
+ + + +
+

+ + + +

+
+
+
+ + + +
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/feature-boxes-with-button.php b/wp-content/themes/ollie/patterns/feature-boxes-with-button.php new file mode 100644 index 0000000..fba1a1f --- /dev/null +++ b/wp-content/themes/ollie/patterns/feature-boxes-with-button.php @@ -0,0 +1,162 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/feature-boxes-with-icon-dark.php b/wp-content/themes/ollie/patterns/feature-boxes-with-icon-dark.php new file mode 100644 index 0000000..d268996 --- /dev/null +++ b/wp-content/themes/ollie/patterns/feature-boxes-with-icon-dark.php @@ -0,0 +1,162 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+ + + + + + + + + + + + + + + + + + + + + +
+
+ diff --git a/wp-content/themes/ollie/patterns/features-with-emojis.php b/wp-content/themes/ollie/patterns/features-with-emojis.php new file mode 100644 index 0000000..44de07d --- /dev/null +++ b/wp-content/themes/ollie/patterns/features-with-emojis.php @@ -0,0 +1,72 @@ + + +
+
+
+

+ + + +

+ + + +

+
+ + + +
+

+ + + +

+ + + +

+
+ + + +
+

+ + + +

+ + + +

+
+ + + +
+

+ + + +

+ + + +

+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/footer-dark-centered.php b/wp-content/themes/ollie/patterns/footer-dark-centered.php new file mode 100644 index 0000000..c5bd4ee --- /dev/null +++ b/wp-content/themes/ollie/patterns/footer-dark-centered.php @@ -0,0 +1,50 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/footer-dark-minimal.php b/wp-content/themes/ollie/patterns/footer-dark-minimal.php new file mode 100644 index 0000000..09edd97 --- /dev/null +++ b/wp-content/themes/ollie/patterns/footer-dark-minimal.php @@ -0,0 +1,22 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/footer-dark.php b/wp-content/themes/ollie/patterns/footer-dark.php new file mode 100644 index 0000000..997db0b --- /dev/null +++ b/wp-content/themes/ollie/patterns/footer-dark.php @@ -0,0 +1,136 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/footer-light-centered.php b/wp-content/themes/ollie/patterns/footer-light-centered.php new file mode 100644 index 0000000..aa262bb --- /dev/null +++ b/wp-content/themes/ollie/patterns/footer-light-centered.php @@ -0,0 +1,50 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/footer-light-minimal.php b/wp-content/themes/ollie/patterns/footer-light-minimal.php new file mode 100644 index 0000000..e16a3ac --- /dev/null +++ b/wp-content/themes/ollie/patterns/footer-light-minimal.php @@ -0,0 +1,22 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/footer-light.php b/wp-content/themes/ollie/patterns/footer-light.php new file mode 100644 index 0000000..74cd5df --- /dev/null +++ b/wp-content/themes/ollie/patterns/footer-light.php @@ -0,0 +1,132 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/header-dark-with-banner.php b/wp-content/themes/ollie/patterns/header-dark-with-banner.php new file mode 100644 index 0000000..aa2eb8f --- /dev/null +++ b/wp-content/themes/ollie/patterns/header-dark-with-banner.php @@ -0,0 +1,36 @@ + + +
+
+
+ + + + +
+
+
+
+
+ + + + +
+ diff --git a/wp-content/themes/ollie/patterns/header-dark-with-buttons.php b/wp-content/themes/ollie/patterns/header-dark-with-buttons.php new file mode 100644 index 0000000..50fc773 --- /dev/null +++ b/wp-content/themes/ollie/patterns/header-dark-with-buttons.php @@ -0,0 +1,28 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/header-dark.php b/wp-content/themes/ollie/patterns/header-dark.php new file mode 100644 index 0000000..9ab2074 --- /dev/null +++ b/wp-content/themes/ollie/patterns/header-dark.php @@ -0,0 +1,20 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/header-light-with-banner.php b/wp-content/themes/ollie/patterns/header-light-with-banner.php new file mode 100644 index 0000000..d427b54 --- /dev/null +++ b/wp-content/themes/ollie/patterns/header-light-with-banner.php @@ -0,0 +1,36 @@ + + +
+
+
+ + + + +
+
+
+
+
+ + + + +
+ diff --git a/wp-content/themes/ollie/patterns/header-light-with-buttons.php b/wp-content/themes/ollie/patterns/header-light-with-buttons.php new file mode 100644 index 0000000..6ee3230 --- /dev/null +++ b/wp-content/themes/ollie/patterns/header-light-with-buttons.php @@ -0,0 +1,28 @@ + + +
+
+ + +
+
+ + + +
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/header-light.php b/wp-content/themes/ollie/patterns/header-light.php new file mode 100644 index 0000000..1ef2a26 --- /dev/null +++ b/wp-content/themes/ollie/patterns/header-light.php @@ -0,0 +1,20 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/hero-call-to-action-buttons-light.php b/wp-content/themes/ollie/patterns/hero-call-to-action-buttons-light.php new file mode 100644 index 0000000..6a58fe9 --- /dev/null +++ b/wp-content/themes/ollie/patterns/hero-call-to-action-buttons-light.php @@ -0,0 +1,36 @@ + + +
+

+ + + +

+ + + +

+ + + +
+
+ + + +
+
+
+ diff --git a/wp-content/themes/ollie/patterns/hero-call-to-action-buttons.php b/wp-content/themes/ollie/patterns/hero-call-to-action-buttons.php new file mode 100644 index 0000000..54362a1 --- /dev/null +++ b/wp-content/themes/ollie/patterns/hero-call-to-action-buttons.php @@ -0,0 +1,42 @@ + + +
<?php esc_attr_e( 'Person working on laptop', 'ollie' ); ?>
+
+
+
+

+ + + +

+
+ + + +

+ + + +
+
+ + + +
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/hero-dark.php b/wp-content/themes/ollie/patterns/hero-dark.php new file mode 100644 index 0000000..e15c5ce --- /dev/null +++ b/wp-content/themes/ollie/patterns/hero-dark.php @@ -0,0 +1,46 @@ + + +
<?php esc_attr_e( 'Person working on laptop', 'ollie' ); ?>
+
+
+
+

+ + + +

+
+ + + +

+ + + +
+
+ + + +
+
+
+ + + +
<?php esc_attr_e( 'Desktop screenshot', 'ollie' ); ?>
+
+
+ diff --git a/wp-content/themes/ollie/patterns/hero-light.php b/wp-content/themes/ollie/patterns/hero-light.php new file mode 100644 index 0000000..6101f01 --- /dev/null +++ b/wp-content/themes/ollie/patterns/hero-light.php @@ -0,0 +1,46 @@ + + +
+
+
+
+

+ + + +

+
+ + + +

+ + + +
+
+ + + +
+
+
+ + + +
<?php esc_attr_e( 'Desktop screenshot', 'ollie' ); ?>
+
+
+ diff --git a/wp-content/themes/ollie/patterns/hero-text-image-and-logos.php b/wp-content/themes/ollie/patterns/hero-text-image-and-logos.php new file mode 100644 index 0000000..333763e --- /dev/null +++ b/wp-content/themes/ollie/patterns/hero-text-image-and-logos.php @@ -0,0 +1,39 @@ + + +
+
+

+ + +

+ + +

+ + +
+
+ + +
+
+
+ + +
<?php esc_attr_e( 'Desktop preview', 'ollie' ); ?>
+

+
+
+ diff --git a/wp-content/themes/ollie/patterns/image-and-numbered-features.php b/wp-content/themes/ollie/patterns/image-and-numbered-features.php new file mode 100644 index 0000000..57bc00b --- /dev/null +++ b/wp-content/themes/ollie/patterns/image-and-numbered-features.php @@ -0,0 +1,80 @@ + + +
+
+
+
+

+
+
+ + + +
+
+
+

+
+ + + +
+

+ + + +

+
+
+ + + +
+
+

+
+ + + +
+

+ + + +

+
+
+ + + +
+
+

+
+ + + +
+

+ + + +

+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/images/avatar-1.webp b/wp-content/themes/ollie/patterns/images/avatar-1.webp new file mode 100644 index 0000000..917c31b Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/avatar-1.webp differ diff --git a/wp-content/themes/ollie/patterns/images/avatar-2.webp b/wp-content/themes/ollie/patterns/images/avatar-2.webp new file mode 100644 index 0000000..9bca173 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/avatar-2.webp differ diff --git a/wp-content/themes/ollie/patterns/images/avatar-3.webp b/wp-content/themes/ollie/patterns/images/avatar-3.webp new file mode 100644 index 0000000..f3ddc64 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/avatar-3.webp differ diff --git a/wp-content/themes/ollie/patterns/images/avatar-4.webp b/wp-content/themes/ollie/patterns/images/avatar-4.webp new file mode 100644 index 0000000..9660197 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/avatar-4.webp differ diff --git a/wp-content/themes/ollie/patterns/images/avatar-5.webp b/wp-content/themes/ollie/patterns/images/avatar-5.webp new file mode 100644 index 0000000..2e6b5b1 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/avatar-5.webp differ diff --git a/wp-content/themes/ollie/patterns/images/avatar-7.webp b/wp-content/themes/ollie/patterns/images/avatar-7.webp new file mode 100644 index 0000000..b74b772 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/avatar-7.webp differ diff --git a/wp-content/themes/ollie/patterns/images/computer-hands.webp b/wp-content/themes/ollie/patterns/images/computer-hands.webp new file mode 100644 index 0000000..35e4137 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/computer-hands.webp differ diff --git a/wp-content/themes/ollie/patterns/images/desktop.webp b/wp-content/themes/ollie/patterns/images/desktop.webp new file mode 100644 index 0000000..0703ffa Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/desktop.webp differ diff --git a/wp-content/themes/ollie/patterns/images/guy-laptop.webp b/wp-content/themes/ollie/patterns/images/guy-laptop.webp new file mode 100644 index 0000000..f2abd5a Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/guy-laptop.webp differ diff --git a/wp-content/themes/ollie/patterns/images/logo-1.webp b/wp-content/themes/ollie/patterns/images/logo-1.webp new file mode 100644 index 0000000..e74cf13 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/logo-1.webp differ diff --git a/wp-content/themes/ollie/patterns/images/logo-2.webp b/wp-content/themes/ollie/patterns/images/logo-2.webp new file mode 100644 index 0000000..db9d4b7 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/logo-2.webp differ diff --git a/wp-content/themes/ollie/patterns/images/logo-3.webp b/wp-content/themes/ollie/patterns/images/logo-3.webp new file mode 100644 index 0000000..70da106 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/logo-3.webp differ diff --git a/wp-content/themes/ollie/patterns/images/logo-4.webp b/wp-content/themes/ollie/patterns/images/logo-4.webp new file mode 100644 index 0000000..fa7ca0d Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/logo-4.webp differ diff --git a/wp-content/themes/ollie/patterns/images/logo-6.webp b/wp-content/themes/ollie/patterns/images/logo-6.webp new file mode 100644 index 0000000..03914b4 Binary files /dev/null and b/wp-content/themes/ollie/patterns/images/logo-6.webp differ diff --git a/wp-content/themes/ollie/patterns/job-openings.php b/wp-content/themes/ollie/patterns/job-openings.php new file mode 100644 index 0000000..6e70b75 --- /dev/null +++ b/wp-content/themes/ollie/patterns/job-openings.php @@ -0,0 +1,124 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+

+ + + +
+

+
+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+ + + +
+
+
+

+ + + +

+
+ + + +
+
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/large-text-and-text-boxes.php b/wp-content/themes/ollie/patterns/large-text-and-text-boxes.php new file mode 100644 index 0000000..5448a2f --- /dev/null +++ b/wp-content/themes/ollie/patterns/large-text-and-text-boxes.php @@ -0,0 +1,92 @@ + + +
+
+

+ + + +

+
+ + + +
+
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-card-1.php b/wp-content/themes/ollie/patterns/menu-card-1.php new file mode 100644 index 0000000..3718ef1 --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-card-1.php @@ -0,0 +1,80 @@ + + +
+
+
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+
+ + + +
+ + + +
+

+ + + +
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-card-2.php b/wp-content/themes/ollie/patterns/menu-card-2.php new file mode 100644 index 0000000..bc6163f --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-card-2.php @@ -0,0 +1,74 @@ + + +
+
+

+
+ + + +
+ + + +
+
+
+

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-card-3.php b/wp-content/themes/ollie/patterns/menu-card-3.php new file mode 100644 index 0000000..77c3508 --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-card-3.php @@ -0,0 +1,70 @@ + + +
+
+
+

+ + + + +
+ + + +

+
+ + + +
+ + + +
+
+

+ + + + +
+ + + +

+
+ + + +
+ + + +
+
+

+ + + + +
+ + + +

+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-card-4.php b/wp-content/themes/ollie/patterns/menu-card-4.php new file mode 100644 index 0000000..7f405ec --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-card-4.php @@ -0,0 +1,78 @@ + + +
+
+
+
+

+ + + + +
+ + + +
+
+
+
+ + + +
+ + + +
+
+

+ + + + +
+ + + +
+
+
+
+ + + +
+ + + +
+
+

+ + + + +
+ + + +
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-mobile-1.php b/wp-content/themes/ollie/patterns/menu-mobile-1.php new file mode 100644 index 0000000..c2e1b02 --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-mobile-1.php @@ -0,0 +1,98 @@ + + +
+
+

+ + + +
+ + + +
+

+ + + +

+ + + +

+
+
+ + + +
+

+ + + +
+ + + +
+

+ + + +

+ + + +

+
+
+ + + +
+

+ + + +
+ + + +
+

+ + + +

+ + + +

+
+
+ + + +
+
+
+
+ + + + +
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-mobile-2.php b/wp-content/themes/ollie/patterns/menu-mobile-2.php new file mode 100644 index 0000000..473facf --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-mobile-2.php @@ -0,0 +1,66 @@ + + +
+
+
+
+ + + +
+

+ + + + +
+
+ + + +
+
+ + + +
+ + + +
+ + + +
+
+
+ + + +
+ + + +
+
+
+
+ + + + +
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-mobile-3.php b/wp-content/themes/ollie/patterns/menu-mobile-3.php new file mode 100644 index 0000000..64b70b3 --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-mobile-3.php @@ -0,0 +1,94 @@ + + +
+
+
+
+ + + +
+

+ + + + +
+
+
+ + + +
+ + + +
+

+ + + +
+ + + +

+ + + +
+ + + +

+ + + +
+ + + +

+ + + +
+ + + +

+
+ + + +
+ + + +
+ + + + + +
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-mobile-4.php b/wp-content/themes/ollie/patterns/menu-mobile-4.php new file mode 100644 index 0000000..bbf6683 --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-mobile-4.php @@ -0,0 +1,86 @@ + + + + diff --git a/wp-content/themes/ollie/patterns/menu-mobile-5.php b/wp-content/themes/ollie/patterns/menu-mobile-5.php new file mode 100644 index 0000000..cf4ab1e --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-mobile-5.php @@ -0,0 +1,114 @@ + + +
+
+

+ + + +

+ + + +

+ + + +

+ + + +

+
+ + + +
+ + + +
+
+
+

+ + + +
+

+ + + +

+ + + +

+ + + +

+
+
+ + + +
+

+ + + +
+

+ + + +

+ + + +

+ + + +

+
+
+
+
+ + + +
+
+
+ + + +
+ + + + +

+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-mobile-6.php b/wp-content/themes/ollie/patterns/menu-mobile-6.php new file mode 100644 index 0000000..7b63331 --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-mobile-6.php @@ -0,0 +1,108 @@ + + +
+
+ + +
+
+ + + +
+ + + +
+ + + +
+ + + +
+
+
+ + + +
+
+
+

+ + + +
+

+ + + +

+ + + +

+ + + +

+
+
+ + + +
+

+ + + +
+

+ + + +

+ + + +

+ + + +

+
+
+
+
+ + + +
+ + + + +

+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-panel-1.php b/wp-content/themes/ollie/patterns/menu-panel-1.php new file mode 100644 index 0000000..4f74e8f --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-panel-1.php @@ -0,0 +1,179 @@ + + +
+
+
+
+
+
+
+ + + + +
+ + + +
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+
+
+ + + +
+
+ + + + +
+ + + +
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+
+
+
+
+ + + +
+
+
+
+ + + +

+ + + +

+ + + +

 

+
+
+
+
+ + + +
+ + + +
+

+ + + +
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-panel-2.php b/wp-content/themes/ollie/patterns/menu-panel-2.php new file mode 100644 index 0000000..23ed44e --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-panel-2.php @@ -0,0 +1,135 @@ + + +
+
+
+

+ + + +
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+
+ + + +
+

+ + + +
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+ + + +
+

+ + + +
+

+ + + + +
+
+
+
+ + + +
+

+ + + +
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-panel-3.php b/wp-content/themes/ollie/patterns/menu-panel-3.php new file mode 100644 index 0000000..803fdf8 --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-panel-3.php @@ -0,0 +1,121 @@ + + +
+
+
+

+ + + +
+ + + +
+

+ + + +

+ + + +

+ + + +

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + +
+

+ + + +
+ + + +
+

+ + + +

+ + + +

+ + + +

+ + + +

+ + + +

+
+ + + +
+
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/menu-panel-4.php b/wp-content/themes/ollie/patterns/menu-panel-4.php new file mode 100644 index 0000000..489d51f --- /dev/null +++ b/wp-content/themes/ollie/patterns/menu-panel-4.php @@ -0,0 +1,79 @@ + + +
+
+
+
+
+
+
+

+ + + +

+
+ + + +
+
+
+
+
+ + + +
+
+
+

+ + + +

+
+ + + +
+
+
+
+
+
+
+ + + +
+
+
+

+ + + +

+
+ + + +
+
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/numbers-stacked.php b/wp-content/themes/ollie/patterns/numbers-stacked.php new file mode 100644 index 0000000..6d1b4f1 --- /dev/null +++ b/wp-content/themes/ollie/patterns/numbers-stacked.php @@ -0,0 +1,56 @@ + + +
+
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+

+ + + +

+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/numbers.php b/wp-content/themes/ollie/patterns/numbers.php new file mode 100644 index 0000000..606d4a5 --- /dev/null +++ b/wp-content/themes/ollie/patterns/numbers.php @@ -0,0 +1,60 @@ + + +
+
+
+
+

+ + + +

+ + + +

+
+ + + +
+

+ + + +

+ + + +

+
+ + + +
+

+ + + +

+ + + +

+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/page-about.php b/wp-content/themes/ollie/patterns/page-about.php new file mode 100644 index 0000000..9e08e85 --- /dev/null +++ b/wp-content/themes/ollie/patterns/page-about.php @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/wp-content/themes/ollie/patterns/page-blog.php b/wp-content/themes/ollie/patterns/page-blog.php new file mode 100644 index 0000000..6624075 --- /dev/null +++ b/wp-content/themes/ollie/patterns/page-blog.php @@ -0,0 +1,14 @@ + + diff --git a/wp-content/themes/ollie/patterns/page-download.php b/wp-content/themes/ollie/patterns/page-download.php new file mode 100644 index 0000000..4185b84 --- /dev/null +++ b/wp-content/themes/ollie/patterns/page-download.php @@ -0,0 +1,22 @@ + + + + + + + + + + diff --git a/wp-content/themes/ollie/patterns/page-features.php b/wp-content/themes/ollie/patterns/page-features.php new file mode 100644 index 0000000..be65d26 --- /dev/null +++ b/wp-content/themes/ollie/patterns/page-features.php @@ -0,0 +1,20 @@ + + + + + + + + diff --git a/wp-content/themes/ollie/patterns/page-home.php b/wp-content/themes/ollie/patterns/page-home.php new file mode 100644 index 0000000..8660e16 --- /dev/null +++ b/wp-content/themes/ollie/patterns/page-home.php @@ -0,0 +1,30 @@ + + + + + + + + + + + + + + + + + + diff --git a/wp-content/themes/ollie/patterns/page-pricing.php b/wp-content/themes/ollie/patterns/page-pricing.php new file mode 100644 index 0000000..b6fc353 --- /dev/null +++ b/wp-content/themes/ollie/patterns/page-pricing.php @@ -0,0 +1,18 @@ + + + + + + diff --git a/wp-content/themes/ollie/patterns/page-profile.php b/wp-content/themes/ollie/patterns/page-profile.php new file mode 100644 index 0000000..5c1039d --- /dev/null +++ b/wp-content/themes/ollie/patterns/page-profile.php @@ -0,0 +1,60 @@ + + +
+
+
+ + + +
+

+ + + +

+
+ + + + +
+ + + +
+
+
+ + + +
+ + + +
+ + + +
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/post-loop-grid-custom.php b/wp-content/themes/ollie/patterns/post-loop-grid-custom.php new file mode 100644 index 0000000..c2f87da --- /dev/null +++ b/wp-content/themes/ollie/patterns/post-loop-grid-custom.php @@ -0,0 +1,52 @@ + + +
+
+ +
+
+ + + + + + +
+ +
+
+ + + +
+
+ + + + +
+ + + +
+
+
+ diff --git a/wp-content/themes/ollie/patterns/post-loop-grid-default.php b/wp-content/themes/ollie/patterns/post-loop-grid-default.php new file mode 100644 index 0000000..0ede345 --- /dev/null +++ b/wp-content/themes/ollie/patterns/post-loop-grid-default.php @@ -0,0 +1,28 @@ + + +
+
+ + + + +
+ + + +
+
+
+ diff --git a/wp-content/themes/ollie/patterns/post-loop-list.php b/wp-content/themes/ollie/patterns/post-loop-list.php new file mode 100644 index 0000000..bed9635 --- /dev/null +++ b/wp-content/themes/ollie/patterns/post-loop-list.php @@ -0,0 +1,52 @@ + + +
+
+ +
+
+ + + + +
+ + +

+ + +
+
+ + + + + + + +
+
+ + + + +
+ + + +
+
+
+ diff --git a/wp-content/themes/ollie/patterns/pricing-table-3-column.php b/wp-content/themes/ollie/patterns/pricing-table-3-column.php new file mode 100644 index 0000000..683ba73 --- /dev/null +++ b/wp-content/themes/ollie/patterns/pricing-table-3-column.php @@ -0,0 +1,294 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+
+
+
+ + + +

+
+ diff --git a/wp-content/themes/ollie/patterns/pricing-table-with-testimonials.php b/wp-content/themes/ollie/patterns/pricing-table-with-testimonials.php new file mode 100644 index 0000000..26ab92d --- /dev/null +++ b/wp-content/themes/ollie/patterns/pricing-table-with-testimonials.php @@ -0,0 +1,332 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+
+
+
+ + + +

+ + + +
+
+
+
+ + + +
+

+ + + +

+
+
+
+ + + +
+
+
+ + + +
+

+ + + +

+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/pricing-table.php b/wp-content/themes/ollie/patterns/pricing-table.php new file mode 100644 index 0000000..c406970 --- /dev/null +++ b/wp-content/themes/ollie/patterns/pricing-table.php @@ -0,0 +1,198 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+

+ + + +

+
+ + + +
+
+
+ + + +
+
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+ + + +
+ + + +
+

+ + + +

+
+
+
+
+ + + +

+
+ diff --git a/wp-content/themes/ollie/patterns/single-testimonial.php b/wp-content/themes/ollie/patterns/single-testimonial.php new file mode 100644 index 0000000..7859f84 --- /dev/null +++ b/wp-content/themes/ollie/patterns/single-testimonial.php @@ -0,0 +1,38 @@ + + +
+

+ + + +
+ + + +
+
+ + + +
+

+ + + +

+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/team-members.php b/wp-content/themes/ollie/patterns/team-members.php new file mode 100644 index 0000000..9a8d28c --- /dev/null +++ b/wp-content/themes/ollie/patterns/team-members.php @@ -0,0 +1,96 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+
+

+ + + +

+
+ + + + +
+
+ + + +
+
+
+

+ + + +

+
+ + + + +
+
+ + + +
+
+
+

+ + + +

+
+ + + + +
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/template-index-grid.php b/wp-content/themes/ollie/patterns/template-index-grid.php new file mode 100644 index 0000000..e90d2a2 --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-index-grid.php @@ -0,0 +1,19 @@ + + + + +
+ + + diff --git a/wp-content/themes/ollie/patterns/template-index-list.php b/wp-content/themes/ollie/patterns/template-index-list.php new file mode 100644 index 0000000..4c88f31 --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-index-list.php @@ -0,0 +1,19 @@ + + + + +
+ + + diff --git a/wp-content/themes/ollie/patterns/template-page-404.php b/wp-content/themes/ollie/patterns/template-page-404.php new file mode 100644 index 0000000..051276d --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-page-404.php @@ -0,0 +1,28 @@ + + + + +
+

+ + + +

+ + + +
+
+ + + diff --git a/wp-content/themes/ollie/patterns/template-page-archive.php b/wp-content/themes/ollie/patterns/template-page-archive.php new file mode 100644 index 0000000..d5932a8 --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-page-archive.php @@ -0,0 +1,26 @@ + + + + +
+
+ +
+
+ + + +
+ + + diff --git a/wp-content/themes/ollie/patterns/template-page-centered.php b/wp-content/themes/ollie/patterns/template-page-centered.php new file mode 100644 index 0000000..f2aaf1c --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-page-centered.php @@ -0,0 +1,25 @@ + + + + +
+
+ +
+ + +
+ + + diff --git a/wp-content/themes/ollie/patterns/template-page-full.php b/wp-content/themes/ollie/patterns/template-page-full.php new file mode 100644 index 0000000..03b75d7 --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-page-full.php @@ -0,0 +1,19 @@ + + + + +
+ + + diff --git a/wp-content/themes/ollie/patterns/template-page-left-sidebar.php b/wp-content/themes/ollie/patterns/template-page-left-sidebar.php new file mode 100644 index 0000000..67fe47b --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-page-left-sidebar.php @@ -0,0 +1,33 @@ + + + + +
+
+
+ + + +
+ + +
+ + +
+
+
+ + + diff --git a/wp-content/themes/ollie/patterns/template-page-right-sidebar.php b/wp-content/themes/ollie/patterns/template-page-right-sidebar.php new file mode 100644 index 0000000..23fa8f2 --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-page-right-sidebar.php @@ -0,0 +1,33 @@ + + + + +
+
+
+ + +
+ + +
+ + + +
+
+
+ + + diff --git a/wp-content/themes/ollie/patterns/template-page-search.php b/wp-content/themes/ollie/patterns/template-page-search.php new file mode 100644 index 0000000..05588a1 --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-page-search.php @@ -0,0 +1,48 @@ + + + + +
+
+ +
+
+ + + +
+
+ + + + + +
+ + + + + + + + + + + +

+ +
+
+ + + diff --git a/wp-content/themes/ollie/patterns/template-page-wide.php b/wp-content/themes/ollie/patterns/template-page-wide.php new file mode 100644 index 0000000..7539c51 --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-page-wide.php @@ -0,0 +1,25 @@ + + + + +
+
+ +
+ + +
+ + + diff --git a/wp-content/themes/ollie/patterns/template-post-centered.php b/wp-content/themes/ollie/patterns/template-post-centered.php new file mode 100644 index 0000000..be9d8f6 --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-post-centered.php @@ -0,0 +1,103 @@ + + + + +
+
+ + + + + + + +
+ + + + + +
+ +
+
+ + + +
+
+
+
+

+ + +
+ + + + +
+
+ + +
+ +
+
+ + + + + + +
+ + + + +
+ + + +
+ + +
+
+
+ + + +
+
+ +
+ + + +
+ +
+
+ + + diff --git a/wp-content/themes/ollie/patterns/template-post-left-sidebar.php b/wp-content/themes/ollie/patterns/template-post-left-sidebar.php new file mode 100644 index 0000000..d87eba5 --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-post-left-sidebar.php @@ -0,0 +1,113 @@ + + + + +
+
+
+ + + +
+
+ + + + + + + + + +
+
+
+
+ + + +
+
+
+
+ + + +
+
+
+

+ + +
+ + + + +
+
+ + +
+ +
+
+ + + + + + +
+ + + + +
+ + + +
+ + +
+
+
+
+
+ + + +
+
+ +
+ + + +
+ +
+
+ + + diff --git a/wp-content/themes/ollie/patterns/template-post-right-sidebar.php b/wp-content/themes/ollie/patterns/template-post-right-sidebar.php new file mode 100644 index 0000000..12df77a --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-post-right-sidebar.php @@ -0,0 +1,113 @@ + + + + +
+
+
+
+ + + + + + + + + +
+
+ + + +
+
+
+ + + +
+
+
+
+
+
+

+ + +
+ + + + +
+
+ + +
+ +
+
+ + + + + + +
+ + + + +
+ + + +
+ + +
+
+ + + +
+
+
+
+ + + +
+
+ +
+ + + +
+ +
+
+ + + diff --git a/wp-content/themes/ollie/patterns/template-post-wide.php b/wp-content/themes/ollie/patterns/template-post-wide.php new file mode 100644 index 0000000..052520e --- /dev/null +++ b/wp-content/themes/ollie/patterns/template-post-wide.php @@ -0,0 +1,103 @@ + + + + +
+
+ + + + + + + +
+ + + + + +
+ +
+
+ + + +
+
+
+
+

+ + +
+ + + + +
+
+ + +
+ +
+
+ + + + + + +
+ + + + +
+ + + +
+ + +
+
+
+ + + +
+
+ +
+ + + +
+ +
+
+ + + diff --git a/wp-content/themes/ollie/patterns/testimonial-highlight.php b/wp-content/themes/ollie/patterns/testimonial-highlight.php new file mode 100644 index 0000000..4f0d440 --- /dev/null +++ b/wp-content/themes/ollie/patterns/testimonial-highlight.php @@ -0,0 +1,38 @@ + + +
+

+ + + +
+ + + +
+
<?php esc_attr_e( 'Testimonial author avatar', 'ollie' ); ?>
+ + + +
+

+ + + +

+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/testimonials-and-logos.php b/wp-content/themes/ollie/patterns/testimonials-and-logos.php new file mode 100644 index 0000000..b2f504c --- /dev/null +++ b/wp-content/themes/ollie/patterns/testimonials-and-logos.php @@ -0,0 +1,146 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+

+ + + +
+
<?php esc_attr_e( 'Testimonial author avatar', 'ollie' ); ?>
+ + + +
+

+ + + +

+
+
+
+ + + +
+

+ + + +
+
<?php esc_attr_e( 'Testimonial author avatar', 'ollie' ); ?>
+ + + +
+

+ + + +

+
+
+
+ + + +
+

+ + + +
+
<?php esc_attr_e( 'Testimonial author avatar', 'ollie' ); ?>
+ + + +
+

+ + + +

+
+
+
+ + + +
+

+ + + +
+
<?php esc_attr_e( 'Testimonial author avatar', 'ollie' ); ?>
+ + + +
+

+ + + +

+
+
+
+
+ + + +
+

+ + + +
+
<?php esc_attr_e( 'Brand logo', 'ollie' ); ?>
+ + + +
<?php esc_attr_e( 'Brand logo', 'ollie' ); ?>
+ + + +
<?php esc_attr_e( 'Brand logo', 'ollie' ); ?>
+ + + +
<?php esc_attr_e( 'Brand logo', 'ollie' ); ?>
+ + + +
<?php esc_attr_e( 'Brand logo', 'ollie' ); ?>
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/testimonials-with-big-text.php b/wp-content/themes/ollie/patterns/testimonials-with-big-text.php new file mode 100644 index 0000000..585bec4 --- /dev/null +++ b/wp-content/themes/ollie/patterns/testimonials-with-big-text.php @@ -0,0 +1,82 @@ + + +
+
+
+

+ + + +

+ + + +
+ + + +
+

+ + + +

+ + + +

+
+
+ + + +
+
+

+ + + +
+
+ + + +
+

+
+
+
+ + + +
+

+ + + +
+
+ + + +
+

+
+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/testimonials-with-social-links.php b/wp-content/themes/ollie/patterns/testimonials-with-social-links.php new file mode 100644 index 0000000..beba63c --- /dev/null +++ b/wp-content/themes/ollie/patterns/testimonials-with-social-links.php @@ -0,0 +1,102 @@ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+

+ + + + +
+ + + +
+ + + +

+ + + +
+
<?php esc_attr_e( 'Testimonial author avatar', 'ollie' ); ?>
+ + + +
+

+ + + +

+
+
+
+ + + +
+
+

+ + + + +
+ + + +
+ + + +

+ + + +
+
<?php esc_attr_e( 'Testimonial author avatar', 'ollie' ); ?>
+ + + +
+

+ + + +

+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/text-and-image-columns-with-icons.php b/wp-content/themes/ollie/patterns/text-and-image-columns-with-icons.php new file mode 100644 index 0000000..b90bbec --- /dev/null +++ b/wp-content/themes/ollie/patterns/text-and-image-columns-with-icons.php @@ -0,0 +1,76 @@ + + +
+
+
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+ + + +
+ + + +
+
+
+

+ + + +

+
+
+ + + +
+
+

+ + + +

+
+
+
+
+ + + +
+
<?php esc_attr_e( 'Hands typing on computer', 'ollie' ); ?>
+

+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/text-and-image-columns-with-testimonial.php b/wp-content/themes/ollie/patterns/text-and-image-columns-with-testimonial.php new file mode 100644 index 0000000..2f4ae1e --- /dev/null +++ b/wp-content/themes/ollie/patterns/text-and-image-columns-with-testimonial.php @@ -0,0 +1,66 @@ + + +
+
+
+
+

+
+
+ + + +
+
+

+ + + +

+ + + +

+
+ + + +
+
+
+ + + +
+ + + +
+
+ + + +
+

+ + + +

+
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/text-call-to-action-buttons.php b/wp-content/themes/ollie/patterns/text-call-to-action-buttons.php new file mode 100644 index 0000000..e898537 --- /dev/null +++ b/wp-content/themes/ollie/patterns/text-call-to-action-buttons.php @@ -0,0 +1,42 @@ + + +
+
+
+

+ + + +

+
+ + + +
+

+ + + +
+
+ + + +
+
+
+
+
+ diff --git a/wp-content/themes/ollie/patterns/text-call-to-action.php b/wp-content/themes/ollie/patterns/text-call-to-action.php new file mode 100644 index 0000000..a02f92a --- /dev/null +++ b/wp-content/themes/ollie/patterns/text-call-to-action.php @@ -0,0 +1,32 @@ + + +
+
+
+

+ + + +

+
+ + + +
+
+
+
+
+ diff --git a/wp-content/themes/ollie/readme.txt b/wp-content/themes/ollie/readme.txt new file mode 100644 index 0000000..8952e5c --- /dev/null +++ b/wp-content/themes/ollie/readme.txt @@ -0,0 +1,257 @@ +=== Ollie WordPress Block Theme === +Contributors: mmcalister, patrickposner +Tags: blog, portfolio, entertainment, grid-layout, one-column, two-columns, three-columns, four-columns, block-patterns, block-styles, custom-logo, custom-menu, editor-style, featured-images, full-site-editing, full-width-template, rtl-language-support, style-variations, template-editing, theme-options, translation-ready, wide-blocks +Requires at least: 5.8 +Tested up to: 6.7.1 +Requires PHP: 7.2 +Stable tag: 1.5.2 +License: GNU General Public License v3.0 (or later) +License URI: https://www.gnu.org/licenses/gpl-3.0.html + +== Description == + +Launch a blazing-fast, pixel-perfect website with the Ollie WordPress block theme! Ollie features over 50 beautiful pattern designs, 7 full-page pattern layouts, and a fully-customizable design system with global styles. Ollie integrates seamlessly with all of the powerful new WordPress editor features, giving you the most lightweight and powerful website builder on the planet — no expensive page builder plugin required! ✶ Full demo: https://demo.olliewp.com ✶ + +== Changelog == + += 1.5.2 - 8/9/25 = +* Fix issue with desktop dropdown toggle + += 1.5.1 - 8/9/25 = +* Fix issue with mobile menu dropdown toggle +* Remove header selector on navigation styles in case user is not using the Header template part + += 1.5.0 - 7/29/25 = +* Add menu card and mobile menu patterns +* Improve markup on header and footer patterns +* Improve mobile menu overlay styles + += 1.4.9 - 6/20/25 = +* Add styling fix for sticky headers + += 1.4.8 - 4/28/25 = +* Add two new typography variations + += 1.4.7 - 4/27/25 = +* Minor style adjustments for WordPress 6.8 +* Fix text underline rendering bug for Firefox + += 1.4.6 - 4/13/25 = +* Update Mona Sans font with better support for multilingual characters + += 1.4.5 - 4/9/25 = +* Fix loop query bug that prevented cateories from showing correctly + += 1.4.4 - 4/8/25 = +* Fix bug in index and archive template with per page value + += 1.4.3 - 4/1/25 = +* Add flexbox helper class to group blocks +* Remove space gap from blog index +* RTL improvements for mobile menu + += 1.4.2 - 3/17/25 = +* Fix social icons on mobile menu + += 1.4.1 - 3/13/25 = +* Remove duplicate card pattern + += 1.4.0 - 3/13/25 = +* Add small style fixes for WooCommerce +* Add 7 new typography presets +* New mobile navigation design with drop downs +* Clean up pattern collection +* Automate pattern translations +* Improve pricing table pattern designs + += 1.3.4 - 3/5/25 = +* Rename header and footer parts for consistency +* Update page templates to use patterns so we can translate strings +* Add translation strings to all patterns +* Linting improvements on all files + += 1.3.3 - 1/31/25 = +* Improve standalone color palettes to match style variations +* Add standalone Neon color palette from Agency style variation +* Add Brand Alt color variation for buttons + += 1.3.2 - 1/29/25 = +* Improve responsive typography for smaller screens + += 1.3.1 - 1/29/25 = +* Remove extra styles in /styles folder + += 1.3.0 - 12/16/24 = +* Refine color palette names to map better to their contextual use. Read more about the change here: https://olliewp.com/docs/color-palette/ +* Refine existing color palettes with updated colors and new color slot. +* Refine pattern layouts with simpler markup where possible +* Add new Agency color palette for upcoming Agency pattern collection. +* Add style variation for quickly changing button colors. +* Add layer names to patterns + += 1.2.5 - 11/25/24 = +* Fix issue where some font weights aren't being applied + += 1.2.4 - 11/21/24 = +* Fix line height issue in global styles by switching to number-based line heights instead of variables + += 1.2.3 - 11/19/24 = +* Improve line height on post title headings + += 1.2.2 - 11/1/24 = +* Fix header and footer calls for child themes + += 1.2.1 - 10/8/24 = +* Fix image paths in patterns for child themes +* Fix box shadow slug name +* Fix outline button color cascading + += 1.2.0 - 10/2/24 = +* Refresh homepage pattern design +* Update site logo to use paragraph instead of H1 +* Update header and footer to lighter color patterns by default +* Add new hero pattern to homepage +* Clean up pattern library for more consistency +* Clean up navigation block markup +* Clean up header and footer markup + += 1.1.6 - 9/3/24 = +* Remove template restriction on headers +* Remove default full width on form inputs + += 1.1.5 - 8/20/24 = +* Add box shadow support +* Fix image paths in patterns for child themes +* Clean up pagination styles + += 1.1.4 - 7/23/24 = +* Add style improvements for images on mobile + += 1.1.3 - 7/16/24 = +* Remove unnecessary theme.json styles +* Improve line height styling +* Remove unnecessary underline styles +* Add image size fallback + += 1.1.2 - 7/9/24 = +* Remove H1 font size to allow for global styles +* Fix font size on site title +* Fix font size on patterns + += 1.1.1 - 6/27/24 = +* Switch main font to Mona Sans +* Adjust typography scale for new font +* Add new style variations: Creator, Startup, Studio +* Remove Dashicons dependency and rework list styles +* Fix tag wrapping on single post template +* Add active class style in navigation +* Improve pattern typography for new style variations + += 1.1.0 - 10/14/23 = +* Remove custom duotone limitation +* Improve patterns for use in child themes +* Remove unnecessary ollie slug from template part + += 1.0.9 - 10/4/23 = +* Update theme description to remove reference to onboarding wizard +* Add Patrick Posner as a contributor + += 1.0.8 - 10/4/23 = +* Update screenshot + += 1.0.7 - 10/2/23 = +* Remove Ollie onboarding wizard in favor of plugin implementation in the coming weeks + += 1.0.6 - 9/27/23 = +* Replace social icon links with placeholder links + += 1.0.5 - 9/27/23 = +* Remove activation modal and replace it with core admin notice +* Remove site icon setting from onboarding + += 1.0.4 - 9/25/23 = +* Refactor dashboard views +* Remove unused styles + += 1.0.3 - 9/22/23 = +* Clean up footer patterns +* Clean up header patterns +* Add more padding to blog post cards + += 1.0.2 - 9/18/23 = +* Prepare theme for wp.org release +* Prefix pattern categories +* Update theme screenshot +* Remove site title, tagline, and logo upload from dashboard wizard +* Add additional license info in readme.txt +* Fix navigation spacing +* Update blog to two column layout +* Under the hood improvements for security and performance + += 1.0.1 - 8/21/23 = +* Remove site logo conditional in header patterns and revert to site title by default + += 1.0.0 - 8/16/23 = +* Initial public release +* Add Ollie Dashboard and Setup Wizard (Appearance → Ollie) +* Fix block spacing for WordPress 6.3 + += 0.1.4 - 7/17/23 = +* Remove home.html template in favor of traditional set up. Using home.html had benefits, but required users to employ workarounds to get the homepage and blog settings working as expected. Now, to create a homepage layout, choose any page, apply the No Title page template, and add one of the full page patterns found in the pattern modal. +* Fix margin styles on paragraphs and lists for 6.3. +* Add a Blog page pattern. +* Prepare theme for Ollie setup wizard. + += 0.1.3 - 5/19/23 = +* Change Front Page template back to front-page.html for now. Still contemplating the best option here. +* Improve styling on search results page +* Change blog index view from list view to a grid view. +* Improve styling on header font sizes +* Add Page With Sidebar template. + += 0.1.2 - 5/10/23 = +* Change Front Page template to Home template, which makes it a lot easier to control what shows on your homepage. + += 0.1.1 - 5/3/23 = +* Fix image height on single post columns +* Remove post type restriction from header and footer patterns +* Add author profile box pattern +* Remove unnecessary styles from style.css +* Update Ollie Twitter URLs + += 0.1.0 - 3/20/23 = +* Initial beta release + +== Copyright == + +Ollie Theme, (C) 2025 Mike McAlister +Ollie is distributed under the terms of the GNU GPL. + +This program is free software: you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 of the License, or +(at your option) any later version. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +All media licensed under Creative Commons Zero (CC0) https://creativecommons.org/publicdomain/zero/1.0/ + +skateboarding.webp - https://stocksnap.io/photo/skateboarder-sidewalk-NH8J97NEVN +computer-hands.webp - https://stocksnap.io/photo/computer-laptop-FBXB2DA8O7 +avatar-1.webp - https://stocksnap.io/photo/people-man-A3WDGDTBI6 +avatar-2.webp - https://stocksnap.io/photo/urban-fashion-TQAKNY0XO2 +avatar-3.webp - https://stocksnap.io/photo/woman-glasses-7RKWHUXLMQ +avatar-4.webp - https://stocksnap.io/photo/smiling-woman-KS92MVGSXY +avatar-5.webp - https://stocksnap.io/photo/male-professional-6QXAIH13O6 +avatar-7.webp - https://stocksnap.io/photo/woman-business-LERRJPTMHP +desktop.webp - https://stocksnap.io/photo/top-workspace-ZUQSBU4E5B +guy-laptop.webp - https://startupstockphotos.com/photos/office-worker-computer/ + +logo-1.webp, logo-2.webp, logo-3.webp, logo-4.webp, logo-5.webp - created by Mike McAlister and available via CC0. + +Other assets: + +- The Mona Sans font is available via the SIL Open Font License 1.1: https://github.com/github/mona-sans/blob/main/LICENSE diff --git a/wp-content/themes/ollie/screenshot.png b/wp-content/themes/ollie/screenshot.png new file mode 100644 index 0000000..81474de Binary files /dev/null and b/wp-content/themes/ollie/screenshot.png differ diff --git a/wp-content/themes/ollie/style.css b/wp-content/themes/ollie/style.css new file mode 100644 index 0000000..90a3233 --- /dev/null +++ b/wp-content/themes/ollie/style.css @@ -0,0 +1,205 @@ +/* +Theme Name: Ollie +Theme URI: olliewp.com +Author: Mike McAlister +Author URI: mikemcalister.com +Description: Launch a blazing-fast, pixel-perfect website with the Ollie WordPress block theme! Ollie features over 50 beautiful pattern designs, 7 full-page pattern layouts, and a fully-customizable design system with Global Styles. Ollie integrates seamlessly with all of the powerful new WordPress editor features, giving you the most lightweight and powerful website builder on the planet — no expensive page builder plugin required! ✶ Full demo: https://demo.olliewp.com ✶ +Tags: blog, portfolio, entertainment, grid-layout, one-column, two-columns, three-columns, four-columns, block-patterns, block-styles, custom-logo, custom-menu, editor-style, featured-images, full-site-editing, full-width-template, rtl-language-support, style-variations, template-editing, theme-options, translation-ready, wide-blocks +Tested up to: 6.7.1 +Requires PHP: 7.3 +Version: 1.5.2 +License: GNU General Public License v3 or later +License URI: https://www.gnu.org/licenses/gpl-2.0.html +Text Domain: ollie + +Ollie WordPress Theme, (C) 2025 Mike McAlister. +Ollie is distributed under the terms of the GNU GPL. +*/ + +/* CSS Reset +---------------------------------------------------------------------------- */ + +*, +*::before, +*::after { + box-sizing: inherit; +} + +html { + box-sizing: border-box; + -moz-osx-font-smoothing: grayscale; + -webkit-font-smoothing: antialiased; +} + +b, +strong { + font-weight: var(--wp--custom--font-weight--semi-bold); +} + +ol, +ul { + padding: 0; +} + +ol:where(:not([class*="wp-block"])), +ol:where(.wp-block-list), +ul:where(:not([class*="wp-block"])), +ul:where(.wp-block-list) { + padding-inline-start: var(--wp--preset--spacing--medium); +} + +div[class*="wp-block-"] figcaption { + color: var(--wp--preset--color--secondary); + font-size: var(--wp--preset--font-size--x-small); + margin-bottom: 0; + margin-top: 20px; + text-align: center; +} + +img, +figure { + max-width: 100%; + height: auto; +} + +/* Temporary fix for text-decoration-thickness in Firefox */ +@-moz-document url-prefix() { + a { + text-decoration-thickness: .1rem !important; + } +} + +/* Standardize form styling +--------------------------------------------- */ + +input, +button, +textarea, +select { + font: inherit; +} + +input[type="button"], +input[type="email"], +input[type="search"], +input[type="submit"], +input[type="text"], +textarea { + -webkit-appearance: none; + appearance: none; +} + +input:not([type="submit"]), +select, +textarea, +.wp-block-post-comments-form input:not([type="submit"]):not([type="checkbox"]), +.wp-block-post-comments-form textarea { + color: var(--wp--preset--color--main); + border-radius: 5px; + border: solid 1px var(--wp--preset--color--border-light); + padding: .5em 1em; + font-size: var(--wp--preset--font-size--small); + background-color: #fff; +} + +input:focus-visible, +textarea:focus-visible { + outline-color: var(--wp--preset--color--primary); +} + +input[type="checkbox"], +input[type="image"], +input[type="radio"] { + width: auto; +} + +label { + width: 100%; + display: block; +} + +::placeholder { + color: var(--wp--preset--color--secondary); + font-size: var(--wp--preset--font-size--small); + opacity: 0.75; +} + +/* Helper styles +---------------------------------------------------------------------------- */ + +a.more-link { + display: block; +} + +/* Inline code */ +*:not(.wp-block-code) > code { + background-color: var(--wp--preset--color--tertiary); + padding: 3px 5px; + position: relative; + border-radius: 3px; +} + +.wp-block-categories { + position: relative; +} + +/* Adjust terms at bottom of posts */ +.single .wp-block-group .wp-block-post-terms, +.blog .wp-block-group .wp-block-post-terms { + margin-bottom: -8px !important; +} + +/* Remove margin on term description on archive pages */ +.wp-block-term-description p:last-child { + margin-bottom: 0; +} + +/* Remove last separator on post list */ +.remove-border-and-padding .wp-block-post-template li:last-child .wp-block-separator { + display: none; +} + +/* Hide post meta div if no tags assigned */ +.single .wp-block-group:has(> .post-meta:empty) { + display: none; +} + +.wp-block-group:empty:has(+ .comment-respond) { + display: none; +} + +.row-logos > figure { + flex-shrink: 1 !important; +} + +/* Sticky header */ + +header:has(>.is-position-sticky) { + position: sticky; + top: calc( 0px + var( --wp-admin--admin-bar--height, 0px ) ); + z-index: 100; +} + +/* Account for admin bar on mobile */ + +@media (max-width: 600px) { + header:has(>.is-position-sticky) { + top: 0; + } +} + +/* Mobile helper classes */ + +@media (max-width: 781px) { + .ollie-hide-on-mobile { + display: none; + } + + .ollie-unstick-mobile { + position: static; + } + + header:has(>.ollie-unstick-mobile) { + position: static; + } +} diff --git a/wp-content/themes/ollie/styles/agency.json b/wp-content/themes/ollie/styles/agency.json new file mode 100644 index 0000000..affdc63 --- /dev/null +++ b/wp-content/themes/ollie/styles/agency.json @@ -0,0 +1,186 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Agency", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#495148" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#e5f0e4" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#CEF453" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#44473b" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#0E0E0E" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#D0D1CD" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#51524e" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#F5F5F0" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#E2E2D9" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#444B57" + } + ] + }, + "typography": { + "fontSizes": [ + { + "fluid": { + "min": ".825rem", + "max": ".95rem" + }, + "size": ".95rem", + "slug": "x-small", + "name": "Extra Small" + }, + { + "fluid": { + "min": ".9rem", + "max": "1.05rem" + }, + "size": "1.05rem", + "slug": "small", + "name": "Small" + }, + { + "fluid": { + "min": "1rem", + "max": "1.125rem" + }, + "size": "1.125rem", + "slug": "base", + "name": "Base" + }, + { + "fluid": { + "min": "1.25rem", + "max": "1.75rem" + }, + "size": "1.75rem", + "slug": "medium", + "name": "Medium" + }, + { + "fluid": { + "min": "1.85rem", + "max": "2.75rem" + }, + "size": "2.75rem", + "slug": "large", + "name": "Large" + }, + { + "fluid": { + "min": "2.85rem", + "max": "4.25rem" + }, + "size": "4.25rem", + "slug": "x-large", + "name": "Extra Large" + }, + { + "fluid": { + "min": "4.5rem", + "max": "6.5rem" + }, + "size": "6.5rem", + "slug": "xx-large", + "name": "Extra Extra Large" + } + ] + } + }, + "styles": { + "elements": { + "button": { + "border": { + "radius": "10px" + }, + "typography": { + "fontFamily": "var(--wp--preset--font-family--narrow)", + "fontSize": "1.225rem", + "fontWeight": "700", + "textTransform": "uppercase" + }, + "color": { + "background": "var(--wp--preset--color--primary-alt)", + "text": "var(--wp--preset--color--main)" + }, + ":hover": { + "color": { + "background": "var(--wp--preset--color--primary-alt)", + "text": "var(--wp--preset--color--main)" + }, + "typography": { + "textDecoration": "underline" + } + }, + "spacing": { + "padding": { + "top": ".75rem", + "right": "1.5rem", + "bottom": ".75rem", + "left": "1.5rem" + } + } + }, + "h1": { + "typography": { + "lineHeight": "var(--wp--custom--line-height--tight)" + } + }, + "h2": { + "typography": { + "lineHeight": "var(--wp--custom--line-height--tight)" + } + }, + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--narrow)", + "fontWeight": "700", + "lineHeight": "var(--wp--custom--line-height--tight)" + } + } + } + } +} diff --git a/wp-content/themes/ollie/styles/blocks/button/button-brand-1.json b/wp-content/themes/ollie/styles/blocks/button/button-brand-1.json new file mode 100644 index 0000000..202dce7 --- /dev/null +++ b/wp-content/themes/ollie/styles/blocks/button/button-brand-1.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Brand", + "slug": "button-brand", + "blockTypes": ["core/button"], + "styles": { + "color": { + "background": "var:preset|color|primary", + "text": "var:preset|color|base" + } + } +} diff --git a/wp-content/themes/ollie/styles/blocks/button/button-brand-2.json b/wp-content/themes/ollie/styles/blocks/button/button-brand-2.json new file mode 100644 index 0000000..54e2af0 --- /dev/null +++ b/wp-content/themes/ollie/styles/blocks/button/button-brand-2.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Brand Alt", + "slug": "button-brand-alt", + "blockTypes": ["core/button"], + "styles": { + "color": { + "background": "var:preset|color|primary-alt", + "text": "var:preset|color|primary-alt-accent" + } + } +} diff --git a/wp-content/themes/ollie/styles/blocks/button/button-dark.json b/wp-content/themes/ollie/styles/blocks/button/button-dark.json new file mode 100644 index 0000000..289f78b --- /dev/null +++ b/wp-content/themes/ollie/styles/blocks/button/button-dark.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Dark", + "slug": "button-dark", + "blockTypes": ["core/button"], + "styles": { + "color": { + "background": "var:preset|color|main", + "text": "var:preset|color|base" + } + } +} diff --git a/wp-content/themes/ollie/styles/blocks/button/button-light.json b/wp-content/themes/ollie/styles/blocks/button/button-light.json new file mode 100644 index 0000000..13f2aed --- /dev/null +++ b/wp-content/themes/ollie/styles/blocks/button/button-light.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Light", + "slug": "button-light", + "blockTypes": ["core/button"], + "styles": { + "color": { + "background": "var:preset|color|base", + "text": "var:preset|color|main" + } + } +} diff --git a/wp-content/themes/ollie/styles/blocks/button/button-tint.json b/wp-content/themes/ollie/styles/blocks/button/button-tint.json new file mode 100644 index 0000000..1a57d56 --- /dev/null +++ b/wp-content/themes/ollie/styles/blocks/button/button-tint.json @@ -0,0 +1,13 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Tint", + "slug": "secondary-button", + "blockTypes": ["core/button"], + "styles": { + "color": { + "background": "var:preset|color|tertiary", + "text": "var:preset|color|main" + } + } +} diff --git a/wp-content/themes/ollie/styles/colors/blue.json b/wp-content/themes/ollie/styles/colors/blue.json new file mode 100644 index 0000000..aba4729 --- /dev/null +++ b/wp-content/themes/ollie/styles/colors/blue.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Blue", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#465aff" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#DBDDFF" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#B1C2FF" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#263042" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#171A1F" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#B6C7D9" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#3b5570" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#F8F7F9" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#DADEE3" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#444B57" + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/colors/green.json b/wp-content/themes/ollie/styles/colors/green.json new file mode 100644 index 0000000..7057941 --- /dev/null +++ b/wp-content/themes/ollie/styles/colors/green.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Green", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#00786f" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#cbdad9" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#92D8D8" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#243737" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#0F1C1C" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#d0e5e5" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#385353" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#f3f8f8" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#D6E5E5" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#293f3f" + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/colors/neon.json b/wp-content/themes/ollie/styles/colors/neon.json new file mode 100644 index 0000000..395efd0 --- /dev/null +++ b/wp-content/themes/ollie/styles/colors/neon.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Neon", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#495148" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#e5f0e4" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#CEF453" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#44473b" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#0E0E0E" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#D0D1CD" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#51524e" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#F5F5F0" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#E2E2D9" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#444B57" + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/colors/orange.json b/wp-content/themes/ollie/styles/colors/orange.json new file mode 100644 index 0000000..1a9fa9c --- /dev/null +++ b/wp-content/themes/ollie/styles/colors/orange.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Orange", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#FF6637" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#FFEBE0" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#FFB281" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#453029" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#211916" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#ECD7CF" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#645048" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#fff6f4" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#EEE3DC" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#60514B" + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/colors/pink.json b/wp-content/themes/ollie/styles/colors/pink.json new file mode 100644 index 0000000..3f231d5 --- /dev/null +++ b/wp-content/themes/ollie/styles/colors/pink.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Pink", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#FF50A9" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#FFE7F3" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#FFCFD7" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#463235" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#1E181B" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#E6CEDA" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#6c4659" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#F7F3F5" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#EDE0E6" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#5D4D55" + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/colors/red.json b/wp-content/themes/ollie/styles/colors/red.json new file mode 100644 index 0000000..5c77ec1 --- /dev/null +++ b/wp-content/themes/ollie/styles/colors/red.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Red", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#F82F58" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#FFE0E8" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#FFD3DC" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#7A424E" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#211719" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#f2dbdf" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#7B5D65" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#fff5f8" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#F0E0E4" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#685458" + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/colors/teal.json b/wp-content/themes/ollie/styles/colors/teal.json new file mode 100644 index 0000000..aa3c462 --- /dev/null +++ b/wp-content/themes/ollie/styles/colors/teal.json @@ -0,0 +1,66 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Teal", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#45A1B8" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#D8F1F8" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#CFF1FA" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#3B646F" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#192123" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#C5DFE6" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#3F595D" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#f1f7f8" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#D2E2E6" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#516063" + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/creator.json b/wp-content/themes/ollie/styles/creator.json new file mode 100644 index 0000000..3ffa68a --- /dev/null +++ b/wp-content/themes/ollie/styles/creator.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Creator", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#5A20FF" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#E2D8FF" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#E3D0FF" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#2E2738" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#1E0E2E" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#E6DBF2" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#695280" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#F8F3FC" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#E6D7F1" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#473856" + } + ] + } + }, + "styles": { + "elements": { + "button": { + "spacing": { + "padding": { + "top": ".6em", + "right": "1.4em", + "bottom": ".6em", + "left": "1.4em" + } + } + }, + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--condensed)", + "fontWeight": "700", + "lineHeight": "var(--wp--custom--line-height--tight)", + "fontSize": "var(--wp--preset--font-size--large)" + } + } + } + } +} diff --git a/wp-content/themes/ollie/styles/startup.json b/wp-content/themes/ollie/styles/startup.json new file mode 100644 index 0000000..2ac9fb9 --- /dev/null +++ b/wp-content/themes/ollie/styles/startup.json @@ -0,0 +1,88 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Startup", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#454DFF" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#DBDDFF" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#B1C2FF" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#263042" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#171A1F" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#B6C7D9" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#3b5570" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#F8F7F9" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#DADEE3" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#444B57" + } + ] + } + }, + "styles": { + "elements": { + "button": { + "spacing": { + "padding": { + "top": ".6em", + "right": "1.4em", + "bottom": ".6em", + "left": "1.4em" + } + } + }, + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--expanded)", + "fontWeight": "500", + "lineHeight": "var(--wp--custom--line-height--snug)", + "fontSize": "var(--wp--preset--font-size--large)" + } + } + } + } +} diff --git a/wp-content/themes/ollie/styles/studio.json b/wp-content/themes/ollie/styles/studio.json new file mode 100644 index 0000000..84e22f6 --- /dev/null +++ b/wp-content/themes/ollie/styles/studio.json @@ -0,0 +1,94 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "title": "Studio", + "settings": { + "color": { + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#FF50A9" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#FFE7F3" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#FFCFD7" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#463235" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#1E181B" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#E6CEDA" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#6c4659" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#F7F3F5" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#EDE0E6" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#5D4D55" + } + ] + } + }, + "styles": { + "elements": { + "button": { + "border": { + "radius": "100px" + }, + "typography": { + "fontSize": "var(--wp--preset--font-size--base)", + "fontWeight": "var(--wp--custom--font-weight--semi-bold)" + }, + "spacing": { + "padding": { + "top": ".6em", + "right": "1.4em", + "bottom": ".6em", + "left": "1.4em" + } + } + }, + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--primary)", + "fontWeight": "800", + "lineHeight": "var(--wp--custom--line-height--snug)" + } + } + } + } +} diff --git a/wp-content/themes/ollie/styles/typography/typography-preset-1.json b/wp-content/themes/ollie/styles/typography/typography-preset-1.json new file mode 100644 index 0000000..293fa89 --- /dev/null +++ b/wp-content/themes/ollie/styles/typography/typography-preset-1.json @@ -0,0 +1,56 @@ +{ + "version": 3, + "$schema": "https://schemas.wp.org/wp/6.7/theme.json", + "title": "Preset 1", + "slug": "typography-preset-1", + "styles": { + "typography": { + "fontFamily":"var(--wp--preset--font-family--mona-sans)" + }, + "elements": { + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--mona-sans-expanded)", + "fontWeight": "500" + } + } + } + }, + "settings": { + "typography": { + "fontFamilies": [ + { + "fontFamily": "Mona Sans, sans-serif", + "name": "Mona Sans", + "slug": "mona-sans", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/mona-sans/Mona-Sans.woff2"] + } + ] + }, + { + "fontFamily": "Mona Sans Expanded, sans-serif", + "name": "Mona Sans Expanded", + "slug": "mona-sans-expanded", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans Expanded", + "fontStretch": "125%", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ + "file:./assets/fonts/mona-sans/Mona-Sans.woff2" + ] + } + ] + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/typography/typography-preset-2.json b/wp-content/themes/ollie/styles/typography/typography-preset-2.json new file mode 100644 index 0000000..1e34e27 --- /dev/null +++ b/wp-content/themes/ollie/styles/typography/typography-preset-2.json @@ -0,0 +1,40 @@ +{ + "version": 3, + "$schema": "https://schemas.wp.org/wp/6.7/theme.json", + "title": "Preset 2", + "slug": "typography-preset-2", + "styles": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--dm-sans)", + "fontWeight": "400" + }, + "elements": { + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--dm-sans)", + "fontWeight": "700" + } + } + } + }, + "settings": { + "typography": { + "fontFamilies": [ + { + "fontFamily": "DM Sans, sans-serif", + "name": "DM Sans", + "slug": "dm-sans", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "DM Sans", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/dm-sans/DMSans-VariableFont_opsz,wght.woff2"] + } + ] + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/typography/typography-preset-3.json b/wp-content/themes/ollie/styles/typography/typography-preset-3.json new file mode 100644 index 0000000..76fe6cc --- /dev/null +++ b/wp-content/themes/ollie/styles/typography/typography-preset-3.json @@ -0,0 +1,52 @@ +{ + "version": 3, + "$schema": "https://schemas.wp.org/wp/6.7/theme.json", + "title": "Preset 3", + "slug": "typography-preset-3", + "styles": { + "typography": { + "fontFamily":"var(--wp--preset--font-family--mona-sans)" + }, + "elements": { + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--big-shoulders)" + } + } + } + }, + "settings": { + "typography": { + "fontFamilies": [ + { + "fontFamily": "Mona Sans, sans-serif", + "name": "Mona Sans", + "slug": "mona-sans", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/mona-sans/Mona-Sans.woff2"] + } + ] + }, + { + "fontFamily": "Big Shoulders, sans-serif", + "name": "Big Shoulders", + "slug": "big-shoulders", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Big Shoulders", + "fontStyle": "normal", + "fontWeight": "100 900", + "src": [ "file:./assets/fonts/big-shoulders/BigShoulders-VariableFont_opsz,wght.woff2"] + } + ] + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/typography/typography-preset-4.json b/wp-content/themes/ollie/styles/typography/typography-preset-4.json new file mode 100644 index 0000000..dc6aca1 --- /dev/null +++ b/wp-content/themes/ollie/styles/typography/typography-preset-4.json @@ -0,0 +1,40 @@ +{ + "version": 3, + "$schema": "https://schemas.wp.org/wp/6.7/theme.json", + "title": "Preset 4", + "slug": "typography-preset-4", + "styles": { + "typography": { + "fontFamily":"var(--wp--preset--font-family--space-grotesk)", + "fontWeight": "400" + }, + "elements": { + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--space-grotesk)", + "fontWeight": "700" + } + } + } + }, + "settings": { + "typography": { + "fontFamilies": [ + { + "fontFamily": "Space Grotesk, sans-serif", + "name": "Space Grotesk", + "slug": "space-grotesk", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Space Grotesk", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/space-grotesk/SpaceGrotesk-VariableFont_wght.woff2"] + } + ] + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/typography/typography-preset-5.json b/wp-content/themes/ollie/styles/typography/typography-preset-5.json new file mode 100644 index 0000000..6fa25c1 --- /dev/null +++ b/wp-content/themes/ollie/styles/typography/typography-preset-5.json @@ -0,0 +1,54 @@ +{ + "version": 3, + "$schema": "https://schemas.wp.org/wp/6.7/theme.json", + "title": "Preset 5", + "slug": "typography-preset-5", + "styles": { + "typography": { + "fontFamily":"var(--wp--preset--font-family--source-serif)", + "fontWeight": "400" + }, + "elements": { + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--montagu-slab)", + "letterSpacing": "-1px" + } + } + } + }, + "settings": { + "typography": { + "fontFamilies": [ + { + "fontFamily": "Montagu Slab, serif", + "name": "Montagu Slab", + "slug": "montagu-slab", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Montagu Slab", + "fontStyle": "normal", + "fontWeight": "300 700", + "src": [ "file:./assets/fonts/montagu-slab/MontaguSlab-VariableFont_opsz,wght.woff2"] + } + ] + }, + { + "fontFamily": "'Source Serif 4'", + "name": "Source Serif 4", + "slug": "source-serif", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "'Source Serif 4'", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/source-serif/SourceSerif4-VariableFont_opsz,wght.woff2"] + } + ] + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/typography/typography-preset-6.json b/wp-content/themes/ollie/styles/typography/typography-preset-6.json new file mode 100644 index 0000000..6b2b25c --- /dev/null +++ b/wp-content/themes/ollie/styles/typography/typography-preset-6.json @@ -0,0 +1,54 @@ +{ + "version": 3, + "$schema": "https://schemas.wp.org/wp/6.7/theme.json", + "title": "Preset 6", + "slug": "typography-preset-6", + "styles": { + "typography": { + "fontFamily":"var(--wp--preset--font-family--mona-sans)", + "fontWeight": "400" + }, + "elements": { + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--fraunces)", + "fontWeight": "700" + } + } + } + }, + "settings": { + "typography": { + "fontFamilies": [ + { + "fontFamily": "Fraunces, serif", + "name": "Fraunces", + "slug": "fraunces", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Fraunces", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/fraunces/Fraunces-VariableFont_SOFT,WONK,opsz,wght.woff2"] + } + ] + }, + { + "fontFamily": "Mona Sans, sans-serif", + "name": "Mona Sans", + "slug": "mona-sans", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/mona-sans/Mona-Sans.woff2"] + } + ] + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/typography/typography-preset-7.json b/wp-content/themes/ollie/styles/typography/typography-preset-7.json new file mode 100644 index 0000000..2cfdf48 --- /dev/null +++ b/wp-content/themes/ollie/styles/typography/typography-preset-7.json @@ -0,0 +1,40 @@ +{ + "version": 3, + "$schema": "https://schemas.wp.org/wp/6.7/theme.json", + "title": "Preset 7", + "slug": "typography-preset-7", + "styles": { + "typography": { + "fontFamily":"var(--wp--preset--font-family--source-serif)", + "fontWeight": "400" + }, + "elements": { + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--source-serif)", + "fontWeight": "800" + } + } + } + }, + "settings": { + "typography": { + "fontFamilies": [ + { + "fontFamily": "'Source Serif 4'", + "name": "Source Serif 4", + "slug": "source-serif", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "'Source Serif 4'", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/source-serif/SourceSerif4-VariableFont_opsz,wght.woff2"] + } + ] + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/typography/typography-preset-8.json b/wp-content/themes/ollie/styles/typography/typography-preset-8.json new file mode 100644 index 0000000..a2558f7 --- /dev/null +++ b/wp-content/themes/ollie/styles/typography/typography-preset-8.json @@ -0,0 +1,67 @@ +{ + "version": 3, + "$schema": "https://schemas.wp.org/wp/6.7/theme.json", + "title": "Preset 8", + "slug": "typography-preset-8", + "styles": { + "typography": { + "fontFamily":"var(--wp--preset--font-family--mona-sans)" + }, + "elements": { + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--mona-sans)", + "fontWeight": "800", + "lineHeight": "var(--wp--custom--line-height--snug)" + } + }, + "h1": { + "typography": { + "letterSpacing": "-1px" + } + }, + "h2": { + "typography": { + "letterSpacing": "-1px" + } + } + } + }, + "settings": { + "typography": { + "fontFamilies": [ + { + "fontFamily": "Mona Sans, sans-serif", + "name": "Mona Sans", + "slug": "mona-sans", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/mona-sans/Mona-Sans.woff2"] + } + ] + }, + { + "fontFamily": "Mona Sans Expanded, sans-serif", + "name": "Mona Sans Expanded", + "slug": "mona-sans-expanded", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans Expanded", + "fontStretch": "125%", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ + "file:./assets/fonts/mona-sans/Mona-Sans.woff2" + ] + } + ] + } + ] + } + } +} diff --git a/wp-content/themes/ollie/styles/typography/typography-preset-9.json b/wp-content/themes/ollie/styles/typography/typography-preset-9.json new file mode 100644 index 0000000..9accc3a --- /dev/null +++ b/wp-content/themes/ollie/styles/typography/typography-preset-9.json @@ -0,0 +1,122 @@ +{ + "version": 3, + "$schema": "https://schemas.wp.org/wp/6.7/theme.json", + "title": "Preset 9", + "slug": "typography-preset-9", + "styles": { + "typography": { + "fontFamily":"var(--wp--preset--font-family--mona-sans)" + }, + "elements": { + "heading": { + "typography": { + "fontFamily": "var(--wp--preset--font-family--mona-sans-narrow)", + "fontWeight": "700", + "lineHeight": "var(--wp--custom--line-height--tight)" + } + } + } + }, + "settings": { + "typography": { + "fontFamilies": [ + { + "fontFamily": "Mona Sans, sans-serif", + "name": "Mona Sans", + "slug": "mona-sans", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/mona-sans/Mona-Sans.woff2"] + } + ] + }, + { + "fontFamily": "Mona Sans Narrow, sans-serif", + "name": "Mona Sans Narrow", + "slug": "mona-sans-narrow", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans Narrow", + "fontStretch": "75%", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ + "file:./assets/fonts/mona-sans/Mona-Sans.woff2" + ] + } + ] + } + ], + "fontSizes": [ + { + "fluid": { + "min": ".825rem", + "max": ".95rem" + }, + "size": ".95rem", + "slug": "x-small", + "name": "Extra Small" + }, + { + "fluid": { + "min": ".9rem", + "max": "1.05rem" + }, + "size": "1.05rem", + "slug": "small", + "name": "Small" + }, + { + "fluid": { + "min": "1rem", + "max": "1.125rem" + }, + "size": "1.125rem", + "slug": "base", + "name": "Base" + }, + { + "fluid": { + "min": "1.25rem", + "max": "1.75rem" + }, + "size": "1.75rem", + "slug": "medium", + "name": "Medium" + }, + { + "fluid": { + "min": "1.85rem", + "max": "2.75rem" + }, + "size": "2.75rem", + "slug": "large", + "name": "Large" + }, + { + "fluid": { + "min": "2.85rem", + "max": "4.25rem" + }, + "size": "4.25rem", + "slug": "x-large", + "name": "Extra Large" + }, + { + "fluid": { + "min": "4.5rem", + "max": "6.5rem" + }, + "size": "6.5rem", + "slug": "xx-large", + "name": "Extra Extra Large" + } + ] + } + } +} diff --git a/wp-content/themes/ollie/templates/404.html b/wp-content/themes/ollie/templates/404.html new file mode 100644 index 0000000..a738576 --- /dev/null +++ b/wp-content/themes/ollie/templates/404.html @@ -0,0 +1 @@ + diff --git a/wp-content/themes/ollie/templates/archive.html b/wp-content/themes/ollie/templates/archive.html new file mode 100644 index 0000000..9233a88 --- /dev/null +++ b/wp-content/themes/ollie/templates/archive.html @@ -0,0 +1 @@ + diff --git a/wp-content/themes/ollie/templates/index.html b/wp-content/themes/ollie/templates/index.html new file mode 100644 index 0000000..f33db0d --- /dev/null +++ b/wp-content/themes/ollie/templates/index.html @@ -0,0 +1 @@ + diff --git a/wp-content/themes/ollie/templates/page-no-title.html b/wp-content/themes/ollie/templates/page-no-title.html new file mode 100644 index 0000000..7471b66 --- /dev/null +++ b/wp-content/themes/ollie/templates/page-no-title.html @@ -0,0 +1 @@ + diff --git a/wp-content/themes/ollie/templates/page-with-sidebar.html b/wp-content/themes/ollie/templates/page-with-sidebar.html new file mode 100644 index 0000000..d98bb46 --- /dev/null +++ b/wp-content/themes/ollie/templates/page-with-sidebar.html @@ -0,0 +1 @@ + diff --git a/wp-content/themes/ollie/templates/page.html b/wp-content/themes/ollie/templates/page.html new file mode 100644 index 0000000..db38143 --- /dev/null +++ b/wp-content/themes/ollie/templates/page.html @@ -0,0 +1 @@ + diff --git a/wp-content/themes/ollie/templates/search.html b/wp-content/themes/ollie/templates/search.html new file mode 100644 index 0000000..ba3c7c5 --- /dev/null +++ b/wp-content/themes/ollie/templates/search.html @@ -0,0 +1 @@ + diff --git a/wp-content/themes/ollie/templates/single.html b/wp-content/themes/ollie/templates/single.html new file mode 100644 index 0000000..396a525 --- /dev/null +++ b/wp-content/themes/ollie/templates/single.html @@ -0,0 +1 @@ + diff --git a/wp-content/themes/ollie/theme.json b/wp-content/themes/ollie/theme.json new file mode 100644 index 0000000..9510b44 --- /dev/null +++ b/wp-content/themes/ollie/theme.json @@ -0,0 +1,908 @@ +{ + "$schema": "https://schemas.wp.org/trunk/theme.json", + "version": 3, + "settings": { + "appearanceTools": true, + "color": { + "defaultDuotone": false, + "defaultGradients": false, + "defaultPalette": false, + "duotone": [ + { + "name": "Blue", + "slug": "blue", + "colors": [ + "#462CFF", + "#ECE8FF" + ] + }, + { + "name": "Pink", + "slug": "Pink", + "colors": [ + "#F22AAA", + "#FFDBF0" + ] + }, + { + "name": "Green", + "slug": "green", + "colors": [ + "#196A64", + "#E0FFFD" + ] + }, + { + "name": "Yellow", + "slug": "yellow", + "colors": [ + "#4B420D", + "#FFE465" + ] + }, + { + "name": "Red", + "slug": "red", + "colors": [ + "#33120B", + "#FF756B" + ] + }, + { + "name": "Teal", + "slug": "teal", + "colors": [ + "#233938", + "#67EBFF" + ] + }, + { + "name": "Orange", + "slug": "orange", + "colors": [ + "#31210D", + "#FFA15B" + ] + }, + { + "name": "Punchy", + "slug": "punchy", + "colors": [ + "#00007e", + "#6aff7e" + ] + }, + { + "name": "Blueberry", + "slug": "blueberry", + "colors": [ + "#7f02d3", + "#00dbfe" + ] + }, + { + "name": "Lime", + "slug": "lime", + "colors": [ + "#02794c", + "#fbf01a" + ] + }, + { + "name": "Sunset", + "slug": "sunset", + "colors": [ + "#FF3253", + "#FFDC53" + ] + }, + { + "name": "Grayscale", + "slug": "grayscale", + "colors": [ + "#000", + "#fff" + ] + } + ], + "gradients": [ + { + "name": "Purple", + "slug": "purple", + "gradient": "linear-gradient(135deg, #4D34FA, #ad34fa)" + }, + { + "name": "Blue", + "slug": "blue", + "gradient": "linear-gradient(135deg, #0057FF, #31B5FF)" + }, + { + "name": "Pink", + "slug": "pink", + "gradient": "linear-gradient(135deg, #FF007A, #FF81BD)" + }, + { + "name": "Black", + "slug": "black", + "gradient": "linear-gradient(135deg, #14111E, #4B4462)" + }, + { + "name": "Heat", + "slug": "heat", + "gradient": "linear-gradient(135deg, #F32758, #FFC581)" + } + ], + "palette": [ + { + "name": "Brand", + "slug": "primary", + "color": "#5344F4" + }, + { + "name": "Brand Accent", + "slug": "primary-accent", + "color": "#e9e7ff" + }, + { + "name": "Brand Alt", + "slug": "primary-alt", + "color": "#DEC9FF" + }, + { + "name": "Brand Alt Accent", + "slug": "primary-alt-accent", + "color": "#3d386b" + }, + { + "name": "Contrast", + "slug": "main", + "color": "#1E1E26" + }, + { + "name": "Contrast Accent", + "slug": "main-accent", + "color": "#d4d4ec" + }, + { + "name": "Base", + "slug": "base", + "color": "#fff" + }, + { + "name": "Base Accent", + "slug": "secondary", + "color": "#545473" + }, + { + "name": "Tint", + "slug": "tertiary", + "color": "#f8f7fc" + }, + { + "name": "Border Base", + "slug": "border-light", + "color": "#E3E3F0" + }, + { + "name": "Border Contrast", + "slug": "border-dark", + "color": "#4E4E60" + } + ], + "link": true + }, + "custom": { + "fontWeight": { + "thin": 100, + "extra-light": 200, + "light": 300, + "regular": 425, + "medium": 500, + "semi-bold": 600, + "bold": 700, + "extra-bold": 800, + "black": 900 + }, + "lineHeight": { + "none": 1, + "tight": 1.1, + "snug": 1.2, + "body": 1.5, + "relaxed": 1.625, + "loose": 2 + } + }, + "layout": { + "contentSize": "740px", + "wideSize": "1260px" + }, + "shadow": { + "defaultPresets": false, + "presets": [ + { + "name": "Extra Large Dark", + "slug": "extra-large-dark", + "shadow": "0px 536px 150px 0px rgba(20, 17, 31, 0.00), 0px 343px 137px 0px rgba(20, 17, 31, 0.01), 0px 193px 116px 0px rgba(20, 17, 31, 0.05), 0px 86px 86px 0px rgba(20, 17, 31, 0.09), 0px 21px 47px 0px rgba(20, 17, 31, 0.10)" + }, + { + "name": "Large Dark", + "slug": "large-dark", + "shadow": "0px 219px 61px 0px rgba(20, 17, 31, 0.00), 0px 140px 56px 0px rgba(20, 17, 31, 0.01), 0px 79px 47px 0px rgba(20, 17, 31, 0.05), 0px 35px 35px 0px rgba(20, 17, 31, 0.09), 0px 9px 19px 0px rgba(20, 17, 31, 0.10)" + }, + { + "name": "Medium Dark", + "slug": "medium-dark", + "shadow": "0px 66px 18px 0px rgba(20, 17, 31, 0.00), 0px 42px 17px 0px rgba(20, 17, 31, 0.01), 0px 24px 14px 0px rgba(20, 17, 31, 0.05), 0px 10px 10px 0px rgba(20, 17, 31, 0.09), 0px 3px 6px 0px rgba(20, 17, 31, 0.10)" + }, + { + "name": "Small Dark", + "slug": "small-dark", + "shadow": "0px 16px 4px 0px rgba(20, 17, 31, 0.00), 0px 10px 4px 0px rgba(20, 17, 31, 0.01), 0px 6px 3px 0px rgba(20, 17, 31, 0.05), 0px 3px 3px 0px rgba(20, 17, 31, 0.09), 0px 1px 1px 0px rgba(20, 17, 31, 0.10)" + }, + { + "name": "Extra Large Light", + "slug": "extra-large-light", + "shadow": "0px 536px 150px 0px rgba(20, 17, 31, 0.00), 0px 343px 137px 0px rgba(20, 17, 31, 0.01), 0px 193px 116px 0px rgba(20, 17, 31, 0.03), 0px 86px 86px 0px rgba(20, 17, 31, 0.04), 0px 21px 47px 0px rgba(20, 17, 31, 0.05)" + }, + { + "name": "Large Light", + "slug": "large-light", + "shadow": "0px 219px 61px 0px rgba(20, 17, 31, 0.00), 0px 140px 56px 0px rgba(20, 17, 31, 0.01), 0px 79px 47px 0px rgba(20, 17, 31, 0.03), 0px 35px 35px 0px rgba(20, 17, 31, 0.04), 0px 9px 19px 0px rgba(20, 17, 31, 0.05)" + }, + { + "name": "Medium Light", + "slug": "medium-light", + "shadow": "0px 69px 19px 0px rgba(20, 17, 31, 0.00), 0px 44px 18px 0px rgba(20, 17, 31, 0.01), 0px 25px 15px 0px rgba(20, 17, 31, 0.03), 0px 11px 11px 0px rgba(20, 17, 31, 0.04), 0px 3px 6px 0px rgba(20, 17, 31, 0.05)" + }, + { + "name": "Small Light", + "slug": "small-light", + "shadow": "0px 16px 5px 0px rgba(20, 17, 31, 0.00), 0px 10px 4px 0px rgba(20, 17, 31, 0.00), 0px 6px 4px 0px rgba(20, 17, 31, 0.02), 0px 3px 3px 0px rgba(20, 17, 31, 0.03), 0px 1px 1px 0px rgba(20, 17, 31, 0.03)" + } + ] + }, + "spacing": { + "defaultSpacingSizes": false, + "spacingSizes": [ + { + "name": "Small", + "size": "clamp(.5rem, 2.5vw, 1rem)", + "slug": "small" + }, + { + "name": "Medium", + "size": "clamp(1.5rem, 4vw, 2rem)", + "slug": "medium" + }, + { + "name": "Large", + "size": "clamp(2rem, 5vw, 3rem)", + "slug": "large" + }, + { + "name": "Extra Large", + "size": "clamp(3rem, 7vw, 5rem)", + "slug": "x-large" + }, + { + "name": "2xl", + "size": "clamp(4rem, 9vw, 7rem)", + "slug": "xx-large" + }, + { + "name": "3xl", + "size": "clamp(5rem, 12vw, 9rem)", + "slug": "xxx-large" + }, + { + "name": "4xl", + "size": "clamp(6rem, 14vw, 13rem)", + "slug": "xxxx-large" + } + ], + "units": [ + "px", + "em", + "rem", + "vh", + "vw", + "%" + ], + "blockGap": true, + "padding": true, + "margin": true + }, + "typography": { + "dropCap": false, + "defaultFontSizes": false, + "fluid": true, + "writingMode": true, + "fontFamilies": [ + { + "fontFamily": "Mona Sans, sans-serif", + "name": "Mona Sans", + "slug": "primary", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans", + "fontStretch": "75% 125%", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ "file:./assets/fonts/mona-sans/Mona-Sans.woff2"] + } + ] + }, + { + "fontFamily": "Mona Sans Expanded, sans-serif", + "name": "Mona Sans Expanded", + "slug": "expanded", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans Expanded", + "fontStretch": "125%", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ + "file:./assets/fonts/mona-sans/Mona-Sans.woff2" + ] + } + ] + }, + { + "fontFamily": "Mona Sans Condensed, sans-serif", + "name": "Mona Sans Condensed", + "slug": "condensed", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans Condensed", + "fontStretch": "94%", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ + "file:./assets/fonts/mona-sans/Mona-Sans.woff2" + ] + } + ] + }, + { + "fontFamily": "Mona Sans Narrow, sans-serif", + "name": "Mona Sans Narrow", + "slug": "narrow", + "fontFace": [ + { + "fontDisplay": "block", + "fontFamily": "Mona Sans Narrow", + "fontStretch": "75%", + "fontStyle": "normal", + "fontWeight": "300 900", + "src": [ + "file:./assets/fonts/mona-sans/Mona-Sans.woff2" + ] + } + ] + }, + { + "fontFamily": "monospace", + "name": "Monospace", + "slug": "monospace" + } + ], + "fontSizes": [ + { + "fluid": { + "min": ".825rem", + "max": ".95rem" + }, + "size": ".95rem", + "slug": "x-small", + "name": "Extra Small" + }, + { + "fluid": { + "min": ".9rem", + "max": "1.05rem" + }, + "size": "1.05rem", + "slug": "small", + "name": "Small" + }, + { + "fluid": { + "min": "1rem", + "max": "1.165rem" + }, + "size": "1.165rem", + "slug": "base", + "name": "Base" + }, + { + "fluid": { + "min": "1.2rem", + "max": "1.65rem" + }, + "size": "1.65rem", + "slug": "medium", + "name": "Medium" + }, + { + "fluid": { + "min": "1.5rem", + "max": "2.75rem" + }, + "size": "2.75rem", + "slug": "large", + "name": "Large" + }, + { + "fluid": { + "min": "1.875rem", + "max": "3.5rem" + }, + "size": "3.5rem", + "slug": "x-large", + "name": "Extra Large" + }, + { + "fluid": { + "min": "2.25rem", + "max": "4.3875rem" + }, + "size": "4.3875rem", + "slug": "xx-large", + "name": "Extra Extra Large" + } + ], + "lineHeight": true + }, + "useRootPaddingAwareAlignments": true + }, + "styles": { + "blocks": { + "core/code": { + "border": { + "radius": "5px", + "width": "0px" + }, + "color": { + "background": "var:preset|color|tertiary", + "text": "var:preset|color|main" + }, + "spacing": { + "padding": { + "top": "var:preset|spacing|medium", + "right": "var:preset|spacing|medium", + "bottom": "var:preset|spacing|medium", + "left": "var:preset|spacing|medium" + } + }, + "typography": { + "fontFamily": "var:preset|font-family|monospace)", + "fontSize": "var:preset|font-size|small" + } + }, + "core/comment-author-name": { + "typography": { + "lineHeight": "var:custom|line-height|body", + "fontWeight": "600" + }, + "elements": { + "link": { + "typography": { + "textDecoration": "none", + "fontSize": "var:preset|font-size|base" + } + } + } + }, + "core/comment-content": { + "spacing": { + "margin": { + "top": "20px !important" + } + } + }, + "core/comment-date": { + "typography": { + "fontSize": "var:preset|font-size|small", + "lineHeight": "var:custom|line-height|body" + }, + "elements": { + "link": { + "typography": { + "textDecoration": "none" + } + } + } + }, + "core/comment-edit-link": { + "typography": { + "fontSize": "var:preset|font-size|small", + "lineHeight": "var:custom|line-height|body" + } + }, + "core/comment-reply-link": { + "typography": { + "fontSize": "var:preset|font-size|small" + } + }, + "core/comments-title": { + "spacing": { + "margin": { + "top": "0px", + "bottom": "var:preset|spacing|large" + } + } + }, + "core/details": { + "css": ".wp-block-details summary { font-weight: 600 }" + }, + "core/post-comments-form": { + "elements": { + "heading": { + "typography": { + "fontSize": "var:preset|font-size|medium" + } + } + } + }, + "core/read-more": { + "elements": { + "link": { + "spacing": { + "margin": { + "top": "200px", + "bottom": "200px" + } + } + } + } + }, + "core/navigation": { + "typography": { + "fontWeight": "500" + }, + "elements": { + "link": { + ":hover": { + "typography": { + "textDecoration": "underline" + } + } + } + } + }, + "core/paragraph": { + "css": "&.has-background { padding: var(--wp--preset--spacing--small) }" + }, + "core/post-template": { + "elements": { + "h2": { + "typography": { + "fontSize": "var:preset|font-size|medium" + } + } + } + }, + "core/post-terms": { + "elements": { + "link": { + "typography": { + "textDecoration": "none" + } + } + } + }, + "core/post-title": { + "elements": { + "link": { + "typography": { + "textDecoration": "none" + }, + ":hover": { + "typography": { + "textDecoration": "underline" + } + } + } + } + }, + "core/preformatted": { + "border": { + "radius": "5px" + }, + "color": { + "background": "var:preset|color|tertiary", + "text": "var:preset|color|main" + }, + "spacing": { + "padding": { + "top": "var:preset|spacing|medium", + "right": "var:preset|spacing|medium", + "bottom": "var:preset|spacing|medium", + "left": "var:preset|spacing|medium" + } + }, + "typography": { + "fontFamily": "var:preset|font-family|monospace)", + "fontSize": "var:preset|font-size|small" + } + }, + "core/pullquote": { + "elements": { + "cite": { + "typography": { + "fontSize": "var:preset|font-size|small" + }, + "color": { + "text": "var:preset|color|secondary" + } + } + }, + "border": { + "color": "var:preset|color|primary", + "top": { + "style": "solid", + "width": "5px" + } + }, + "spacing": { + "padding": { + "top": "0", + "right": "0", + "bottom": "0", + "left": "0" + } + }, + "typography": { + "lineHeight": "var:custom|line-height|body", + "fontSize": "var:preset|font-size|medium", + "fontWeight": "500" + } + }, + "core/query-pagination": { + "elements": { + "link": { + "border": { + "radius": "5px", + "width": "0" + }, + "color": { + "background": "var:preset|color|base" + }, + "spacing": { + "padding": { + "top": ".5em", + "right": "1em", + "bottom": ".5em", + "left": "1em" + } + }, + "typography": { + "fontSize": "var:preset|font-size|small", + "fontWeight": "500", + "textDecoration": "none" + }, + ":hover": { + "typography": { + "textDecoration": "underline" + } + } + } + } + }, + "core/quote": { + "elements": { + "cite": { + "typography": { + "fontSize": "var:preset|font-size|small" + }, + "color": { + "text": "var:preset|color|secondary" + } + } + }, + "css": "& :where(cite) { display: block }", + "border": { + "radius": "0", + "style": "solid", + "width": "0 0 0 5px !important", + "color": "var:preset|color|primary" + }, + "spacing": { + "padding": { + "left": "var:preset|spacing|large", + "right": "var:preset|spacing|large" + }, + "margin": { + "left": "0" + } + }, + "typography": { + "lineHeight": "var:custom|line-height|body", + "fontSize": "var:preset|font-size|medium", + "fontWeight": "500" + } + }, + "core/search": { + "css": ".wp-block-search__button-inside .wp-block-search__inside-wrapper { border: none }" + }, + "core/separator": { + "color": { + "text": "var:preset|color|main" + } + }, + "core/site-tagline": { + "spacing": { + "margin": { + "bottom": "20px" + } + }, + "typography": { + "fontSize": "var:preset|font-size|small" + } + }, + "core/site-title": { + "typography": { + "fontSize": "var:preset|font-size|base", + "fontWeight": "600", + "lineHeight": "var:custom|line-height|none", + "letterSpacing": "0" + }, + "elements": { + "link": { + "typography": { + "textDecoration": "none" + } + } + } + }, + "core/table": { + "typography": { + "fontSize": "var:preset|font-size|small" + } + }, + "core/template-part": { + "spacing": { + "margin": { + "top": "0px !important" + } + } + } + }, + "color": { + "background": "var:preset|color|base", + "text": "var:preset|color|main" + }, + "elements": { + "button": { + "border": { + "radius": "5px", + "width": "0" + }, + "color": { + "background": "var:preset|color|main", + "text": "var:preset|color|base" + }, + "spacing": { + "padding": { + "top": ".6em", + "right": "1em", + "bottom": ".6em", + "left": "1em" + } + }, + "typography": { + "fontSize": "var:preset|font-size|small", + "fontWeight": "500" + }, + ":hover": { + "color": { + "background": "var:preset|color|main", + "text": "var:preset|color|base" + }, + "typography": { + "textDecoration": "underline" + } + } + }, + "h1": { + "typography": { + "fontSize": "var:preset|font-size|x-large", + "lineHeight": "var:custom|line-height|snug" + } + }, + "h2": { + "typography": { + "fontSize": "var:preset|font-size|large", + "lineHeight": "var:custom|line-height|snug" + } + }, + "h3": { + "typography": { + "fontSize": "var:preset|font-size|medium" + } + }, + "h4": { + "typography": { + "fontSize": "var:preset|font-size|base" + } + }, + "h5": { + "typography": { + "fontSize": "var:preset|font-size|small" + } + }, + "h6": { + "typography": { + "fontSize": "var:preset|font-size|x-small" + } + }, + "heading": { + "typography": { + "fontFamily": "var:preset|font-family|primary", + "fontWeight": "600", + "lineHeight": "var:custom|line-height|body" + } + }, + "link": { + "color": { + "text": "var:preset|color|main" + } + } + }, + "spacing": { + "blockGap": "var:preset|spacing|medium", + "padding": { + "top": "0", + "right": "var:preset|spacing|medium", + "bottom": "0", + "left": "var:preset|spacing|medium" + } + }, + "typography": { + "fontFamily": "var:preset|font-family|primary", + "fontSize": "var:preset|font-size|base", + "fontWeight": "430", + "lineHeight": "var:custom|line-height|body" + } + }, + "customTemplates": [ + { + "name": "page-no-title", + "title": "Page (Full Width, No Title)", + "postTypes": [ + "page", + "ollie_pattern", + "post" + ] + }, + { + "name": "page-with-sidebar", + "title": "Page (With Sidebar)", + "postTypes": [ + "post", + "page" + ] + } + ], + "templateParts": [ + { + "name": "header", + "title": "Header", + "area": "header" + }, + { + "name": "footer", + "title": "Footer", + "area": "footer" + }, + { + "name": "sidebar", + "title": "Sidebar", + "area": "sidebar" + } + ] +} diff --git a/wp-content/themes/postsecret/archive-secrets.php b/wp-content/themes/postsecret/archive-secrets.php new file mode 100644 index 0000000..f6d076c --- /dev/null +++ b/wp-content/themes/postsecret/archive-secrets.php @@ -0,0 +1,40 @@ + + +
+

+ + +
+ +
+ + + +

+ +
+ + li { + background: var(--wp--preset--color--surface); + border: 1px solid var(--wp--preset--color--border); + border-radius: 8px; + padding: 1rem; + margin-bottom: 1rem; +} + +/* Card hover effects */ +.wp-block-post:hover, +.wp-block-query .wp-block-post-template > li:hover { + border-color: var(--wp--preset--color--accent); + transform: translateY(-2px); + transition: all 0.2s ease; +} + +/* Image styling for secrets */ +.wp-block-post-featured-image img { + border-radius: 4px; + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1); +} + +:root[data-theme="dark"] .wp-block-post-featured-image img { + box-shadow: 0 2px 8px rgba(0, 0, 0, 0.3); +} + +/* Muted text */ +.wp-block-post-excerpt, +.post-meta, +.secret-meta { + color: var(--wp--preset--color--muted); +} + +/* Focus states */ +button:focus, +a:focus, +input:focus { + outline: 2px solid var(--wp--preset--color--accent); + outline-offset: 2px; +} + +/* Theme toggle button */ +[data-ps-theme-toggle] { + background: var(--wp--preset--color--surface); + border: 1px solid var(--wp--preset--color--border); + color: var(--wp--preset--color--text); + padding: 0.5rem; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s ease; +} + +[data-ps-theme-toggle]:hover { + background: var(--wp--preset--color--accent); + color: var(--wp--preset--color--bg); +} + +/* Secret archive specific styles */ +.secret-grid { + display: grid; + grid-template-columns: repeat(auto-fill, minmax(300px, 1fr)); + gap: 1.5rem; + margin: 2rem 0; +} + +.secret-card { + background: var(--wp--preset--color--surface); + border: 1px solid var(--wp--preset--color--border); + border-radius: 8px; + overflow: hidden; + transition: all 0.2s ease; +} + +.secret-card:hover { + transform: translateY(-4px); + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.1); +} + +:root[data-theme="dark"] .secret-card:hover { + box-shadow: 0 8px 25px rgba(0, 0, 0, 0.3); +} + +.secret-tags { + display: flex; + flex-wrap: wrap; + gap: 0.5rem; + margin-top: 0.5rem; +} + +.secret-tag { + background: var(--wp--preset--color--accent); + color: var(--wp--preset--color--bg); + padding: 0.25rem 0.5rem; + border-radius: 4px; + font-size: 0.75rem; + text-decoration: none; +} + +.secret-tag:hover { + opacity: 0.8; +} + +/* Accessibility improvements */ +@media (prefers-reduced-motion: reduce) { + .wp-block-post:hover, + .wp-block-query .wp-block-post-template > li:hover, + .secret-card:hover, + [data-ps-theme-toggle] { + transform: none; + transition: none; + } +} + +/* Screen reader only text */ +.screen-reader-text { + clip: rect(1px, 1px, 1px, 1px); + position: absolute !important; + height: 1px; + width: 1px; + overflow: hidden; +} + +/* ----------------------- + * Header & Logo Styles + * -----------------------*/ +.ps-header__logo { + display: inline-block; + line-height: 0; + transition: opacity 0.2s ease; +} + +.ps-header__logo:hover { + opacity: 0.8; +} + +.ps-logo { + height: 36px; + width: auto; + transition: opacity 0.2s ease; +} + +/* Social links */ +.ps-social { + display: flex; + gap: 1rem; + align-items: center; +} + +.ps-social__link { + color: var(--wp--preset--color--text); + font-size: 1.25rem; + transition: color 0.2s ease; + text-decoration: none; +} + +.ps-social__link:hover { + color: var(--wp--preset--color--accent); +} + +/* Theme toggle button */ +.ps-theme-toggle .wp-block-button__link { + background: var(--wp--preset--color--surface); + border: 1px solid var(--wp--preset--color--border); + color: var(--wp--preset--color--text); + padding: 0.5rem; + border-radius: 4px; + cursor: pointer; + transition: all 0.2s ease; + font-size: 1rem; +} + +.ps-theme-toggle .wp-block-button__link:hover { + background: var(--wp--preset--color--accent); + color: var(--wp--preset--color--bg); + border-color: var(--wp--preset--color--accent); +} + +/* Search form styling */ +.ps-search { + display: flex; + align-items: center; + background: var(--wp--preset--color--surface); + border: 1px solid var(--wp--preset--color--border); + border-radius: 6px; + padding: 0; + overflow: hidden; + transition: all 0.2s ease; +} + +.ps-search:focus-within { + border-color: var(--wp--preset--color--accent); + box-shadow: 0 0 0 2px rgba(10, 132, 255, 0.1); +} + +.ps-search__input { + background: transparent; + border: none; + padding: 0.5rem 0.75rem; + color: var(--wp--preset--color--text); + font-size: 0.875rem; + width: 200px; + outline: none; +} + +.ps-search__input::placeholder { + color: var(--wp--preset--color--muted); + opacity: 1; +} + +.ps-search__btn { + background: transparent; + border: none; + padding: 0.5rem 0.75rem; + color: var(--wp--preset--color--muted); + cursor: pointer; + display: flex; + align-items: center; + justify-content: center; + transition: color 0.2s ease; +} + +.ps-search__btn:hover { + color: var(--wp--preset--color--accent); +} + +.ps-search__btn:focus { + outline: 2px solid var(--wp--preset--color--accent); + outline-offset: -2px; +} + +/* Dark mode specific overrides for search */ +html[data-theme="dark"] .ps-search__input, +html[data-theme="dark"] .ps-search__btn { + color: var(--wp--preset--color--text); +} + +html[data-theme="dark"] .ps-search__input::placeholder { + color: var(--wp--preset--color--muted); +} + +/* Header responsive */ +@media (max-width: 768px) { + .ps-header { + flex-direction: column; + gap: 1rem; + padding: 1rem; + } + + .ps-header__right { + gap: 1rem; + } + + .ps-social { + gap: 0.75rem; + } + + .ps-social__link { + font-size: 1.125rem; + } + + .ps-search__input { + width: 150px; + } +} + +/* ----------------------- + * Footer Styles + * -----------------------*/ +.ps-footer { + background: var(--wp--preset--color--bg); + color: var(--wp--preset--color--text); + border-top: 1px solid var(--wp--preset--color--border); +} + +/* Footer logo */ +.ps-footer__brand { + display: inline-block; + line-height: 0; + transition: opacity 0.2s ease; +} + +.ps-footer__brand:hover { + opacity: 0.8; +} + +.ps-logo--footer { + height: 28px; + width: auto; + transition: opacity 0.2s ease; +} + +/* Help card styling */ +.ps-help-card { + display: block; + background: var(--wp--preset--color--accent); + color: var(--wp--preset--color--bg); + padding: 1rem 1.5rem; + border-radius: 8px; + text-decoration: none; + font-weight: 500; + text-align: center; + transition: all 0.2s ease; + border: 2px solid var(--wp--preset--color--accent); +} + +.ps-help-card:hover { + background: var(--wp--preset--color--bg); + color: var(--wp--preset--color--accent); + transform: translateY(-1px); + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.1); +} + +html[data-theme="dark"] .ps-help-card:hover { + box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3); +} + +/* Footer theme toggle - smaller, outline style */ +.ps-footer .ps-theme-toggle .wp-block-button__link { + background: transparent; + border: 1px solid var(--wp--preset--color--border); + color: var(--wp--preset--color--text); + padding: 0.375rem; + border-radius: 4px; + font-size: 0.875rem; +} + +.ps-footer .ps-theme-toggle .wp-block-button__link:hover { + background: var(--wp--preset--color--surface); + border-color: var(--wp--preset--color--accent); + color: var(--wp--preset--color--accent); +} + +/* Footer responsive */ +@media (max-width: 768px) { + .ps-footer .wp-block-group { + flex-direction: column; + text-align: center; + gap: 1rem; + } + + .ps-help-card { + padding: 0.875rem 1.25rem; + font-size: 0.9rem; + } +} + +/* ----------------------- + * Share a Secret Section + * -----------------------*/ +.ps-share-section { + display: flex; + justify-content: center; + margin: 0 auto; +} + +.ps-share-link { + display: inline-block; + line-height: 0; + transition: all 0.3s ease; + border-radius: 12px; + overflow: hidden; +} + +.ps-share-link:hover { + transform: translateY(-4px); + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.15); +} + +html[data-theme="dark"] .ps-share-link:hover { + box-shadow: 0 12px 32px rgba(0, 0, 0, 0.4); +} + +.ps-share-image { + width: 100%; + max-width: 880px; + height: auto; + border-radius: 12px; + transition: all 0.3s ease; + border: 1px solid var(--wp--preset--color--border); +} + +.ps-share-link:hover .ps-share-image { + transform: scale(1.02); +} + +.ps-share-link:focus { + outline: 3px solid var(--wp--preset--color--accent); + outline-offset: 4px; + border-radius: 12px; +} + +/* Responsive sizing */ +@media (max-width: 1024px) { + .ps-share-image { + max-width: 100%; + border-radius: 8px; + } + + .ps-share-link { + border-radius: 8px; + } + + .ps-share-link:focus { + border-radius: 8px; + } +} + +@media (max-width: 768px) { + .ps-share-link:hover { + transform: translateY(-2px); + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.12); + } + + html[data-theme="dark"] .ps-share-link:hover { + box-shadow: 0 8px 24px rgba(0, 0, 0, 0.3); + } + + .ps-share-link:hover .ps-share-image { + transform: scale(1.01); + } +} + +/* ----------------------- + * Cutting-edge hero + * -----------------------*/ +.ps-hero { + position: relative; + isolation: isolate; + border: 1px solid var(--wp--preset--color--border); + border-radius: 16px; + overflow: hidden; + background: radial-gradient(1200px 600px at 10% 10%, color-mix(in lab, var(--wp--preset--color--accent) 24%, transparent), transparent 60%), + radial-gradient(900px 500px at 90% 80%, color-mix(in lab, var(--wp--preset--color--accent) 12%, var(--wp--preset--color--surface)), var(--wp--preset--color--surface) 70%); +} +.ps-hero::after { + /* subtle grain for texture */ + content:""; + position:absolute; inset:0; + pointer-events:none; opacity:.12; + background-image: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='160' height='160' viewBox='0 0 160 160'%3E%3Cfilter id='n'%3E%3CfeTurbulence baseFrequency='.8' numOctaves='2'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23n)' opacity='.15'/%3E%3C/svg%3E"); + mix-blend-mode: overlay; +} + +.ps-hero__visual { display:flex; align-items:center; justify-content:center; } + +.ps-hero__card { + position: relative; + display: block; + border-radius: 14px; + overflow: hidden; + transform: rotate(-1.2deg); + border: 1px solid var(--wp--preset--color--border); + box-shadow: 0 10px 40px rgba(0,0,0,.08); + transition: transform .28s ease, box-shadow .28s ease, border-color .28s ease; + will-change: transform; +} +.ps-hero__card:hover { + transform: rotate(0deg) scale(1.02); + box-shadow: 0 20px 60px rgba(0,0,0,.14); + border-color: var(--wp--preset--color--accent); +} +.ps-hero__img { display:block; width:100%; height:auto; } + +.ps-hero__badge { + position: absolute; + left: 16px; bottom: 16px; + padding: 6px 10px; + border-radius: 999px; + background: var(--wp--preset--color--accent); + color: var(--wp--preset--color--bg); + font-weight: 600; font-size: .8rem; letter-spacing:.02em; + box-shadow: 0 6px 22px rgba(0,0,0,.18); +} + +/* Responsive tweaks */ +@media (max-width: 960px) { + .ps-hero__card { transform: rotate(0); } +} + +/* Pill button */ +.ps-pill { + border-radius: 9999px !important; + font-weight: 700; + letter-spacing: .01em; + line-height: 1; + box-shadow: 0 10px 28px rgba(0,0,0,.18); + transition: transform .18s ease, box-shadow .18s ease, background-color .18s ease; +} + +/* Use your theme accent + accessible focus */ +.ps-pill:focus-visible { + outline: 3px solid color-mix(in lab, var(--wp--preset--color--accent) 60%, transparent); + outline-offset: 2px; +} + +.ps-pill:hover { + transform: translateY(-1px); + box-shadow: 0 14px 36px rgba(0,0,0,.22); +} + +/* Dark/light friendly fill using tokens */ +body:not(.is-light) .ps-pill { + background: var(--wp--preset--color--accent); + color: var(--wp--preset--color--bg); +} +.is-light .ps-pill { + background: var(--wp--preset--color--accent); + color: var(--wp--preset--color--bg); +} + +/* Optional: subtle border for high-contrast themes */ +.ps-pill { border: 1px solid color-mix(in lab, var(--wp--preset--color--accent) 35%, transparent); } + +/* Motion respect */ +@media (prefers-reduced-motion: reduce) { + .ps-pill { transition: none; } +} + +/* ----- Secret Metadata Display ----- */ +.ps-secret-metadata { + margin: var(--wp--preset--spacing--50) 0; + display: grid; + gap: var(--wp--preset--spacing--40); +} + +.ps-metadata-section { + background: var(--wp--preset--color--surface); + border-radius: 8px; + padding: var(--wp--preset--spacing--40); + border: 1px solid var(--wp--preset--color--border); +} + +.ps-metadata-title { + font-size: 1.25rem; + font-weight: 600; + margin: 0 0 var(--wp--preset--spacing--30) 0; + color: var(--wp--preset--color--text); +} + +.ps-metadata-grid { + display: grid; + gap: var(--wp--preset--spacing--20); + grid-template-columns: repeat(auto-fit, minmax(280px, 1fr)); +} + +.ps-metadata-item { + display: flex; + flex-direction: column; + gap: 4px; +} + +.ps-metadata-item strong { + font-weight: 500; + color: var(--wp--preset--color--text); + font-size: 0.875rem; +} + +.ps-metadata-item span, +.ps-metadata-item p { + color: var(--wp--preset--color--muted); + font-size: 0.875rem; + margin: 0; +} + +/* Post meta styling */ +.ps-post-tags { + display: flex; + flex-wrap: wrap; + gap: 6px; +} + +.ps-tag { + background: var(--wp--preset--color--accent); + color: var(--wp--preset--color--bg); + padding: 2px 8px; + border-radius: 12px; + font-size: 0.75rem; + text-decoration: none; +} + +/* Status indicators */ +.ps-review-status { + padding: 2px 8px; + border-radius: 4px; + font-size: 0.75rem; + font-weight: 500; + text-transform: uppercase; +} + +.ps-review-status.needs-review { + background: #fef3c7; + color: #92400e; +} + +.ps-review-status.auto-vetted { + background: #d1fae5; + color: #065f46; +} + +.ps-review-status.flagged { + background: #fee2e2; + color: #991b1b; +} + +.ps-pii-status.true { + color: #dc2626; + font-weight: 500; +} + +.ps-pii-status.false { + color: #16a34a; + font-weight: 500; +} + +/* Progress bars */ +.ps-nsfw-display, +.ps-confidence-display { + display: flex; + align-items: center; + gap: 8px; +} + +.ps-nsfw-bar, +.ps-confidence-bar { + flex: 1; + height: 6px; + background: var(--wp--preset--color--border); + border-radius: 3px; + overflow: hidden; +} + +.ps-nsfw-fill { + height: 100%; + background: linear-gradient(90deg, #10b981, #f59e0b, #ef4444); + border-radius: 3px; + transition: width 0.3s ease; +} + +.ps-confidence-fill { + height: 100%; + background: var(--wp--preset--color--accent); + border-radius: 3px; + transition: width 0.3s ease; +} + +/* Classification tags */ +.ps-classification-tags { + display: flex; + flex-wrap: wrap; + gap: 4px; +} + +.ps-classification-tag { + background: var(--wp--preset--color--border); + color: var(--wp--preset--color--text); + padding: 2px 6px; + border-radius: 4px; + font-size: 0.75rem; +} + +/* Responsive adjustments */ +@media (max-width: 768px) { + .ps-metadata-grid { + grid-template-columns: 1fr; + } + + .ps-metadata-section { + padding: var(--wp--preset--spacing--30); + } + + .ps-nsfw-display, + .ps-confidence-display { + flex-direction: column; + align-items: flex-start; + gap: 4px; + } + + .ps-nsfw-bar, + .ps-confidence-bar { + width: 100%; + min-width: 120px; + } +} + +/* Dark mode adjustments */ +html[data-theme="dark"] .ps-metadata-section { + background: var(--wp--preset--color--surface) !important; + border-color: var(--wp--preset--color--border) !important; +} + +html[data-theme="dark"] .ps-review-status.needs-review { + background: #451a03; + color: #fbbf24; +} + +html[data-theme="dark"] .ps-review-status.auto-vetted { + background: #064e3b; + color: #34d399; +} + +html[data-theme="dark"] .ps-review-status.flagged { + background: #7f1d1d; + color: #f87171; +} + +/* ----- Search Results Styling ----- */ +.ps-search-info { + margin-bottom: var(--wp--preset--spacing--30); + padding: var(--wp--preset--spacing--20); + background: var(--wp--preset--color--surface); + border-radius: 6px; + border: 1px solid var(--wp--preset--color--border); +} + +.ps-search-info p { + margin: 0; + color: var(--wp--preset--color--muted); + font-size: 0.875rem; +} + +.ps-search-query strong { + color: var(--wp--preset--color--text); + font-weight: 500; +} + +.ps-search-count { + margin-left: 0.5rem; + font-style: italic; +} + +/* Search term highlighting */ +.ps-highlight { + background: var(--wp--preset--color--accent); + color: var(--wp--preset--color--bg); + padding: 1px 3px; + border-radius: 3px; + font-weight: 500; +} + +/* Search results grid responsive */ +@media (max-width: 768px) { + .wp-block-query .wp-block-post-template { + grid-template-columns: 1fr !important; + } +} + +@media (max-width: 1024px) { + .wp-block-query .wp-block-post-template { + grid-template-columns: repeat(2, 1fr) !important; + } +} + +/* Dark mode search adjustments */ +html[data-theme="dark"] .ps-search-info { + background: var(--wp--preset--color--surface) !important; + border-color: var(--wp--preset--color--border) !important; +} + +html[data-theme="dark"] .ps-highlight { + background: var(--wp--preset--color--accent) !important; + color: var(--wp--preset--color--bg) !important; +} diff --git a/wp-content/themes/postsecret/footer.php b/wp-content/themes/postsecret/footer.php new file mode 100644 index 0000000..2c26aa4 --- /dev/null +++ b/wp-content/themes/postsecret/footer.php @@ -0,0 +1,6 @@ + + + + + + diff --git a/wp-content/themes/postsecret/functions.php b/wp-content/themes/postsecret/functions.php new file mode 100644 index 0000000..dd17460 --- /dev/null +++ b/wp-content/themes/postsecret/functions.php @@ -0,0 +1,107 @@ +post_type === 'secret') { + $metadata = []; + $meta_fields = [ + 'nsfw_score', 'review_status', 'contains_pii', 'language', + 'font_style', 'color_cast', 'exposure', 'classification_json' + ]; + + foreach ($meta_fields as $field) { + $metadata[$field] = get_post_meta($post->ID, $field, true); + } + + // Get taxonomy terms + $tags = get_the_terms($post->ID, 'secret_tag'); + $tag_data = []; + if ($tags && !is_wp_error($tags)) { + foreach ($tags as $tag) { + $tag_data[] = [ + 'name' => $tag->name, + 'slug' => $tag->slug, + 'link' => get_term_link($tag) + ]; + } + } + + wp_localize_script('ps-secret-metadata', 'psSecretData', [ + 'postId' => $post->ID, + 'postDate' => get_the_date('c', $post->ID), + 'metadata' => $metadata, + 'tags' => $tag_data + ]); + } + } + + // Search enhancements script (on search pages) + if (is_search()) { + wp_enqueue_script( + 'ps-search-enhancements', + get_stylesheet_directory_uri() . '/search-enhancements.js', + [], + null, + true + ); + } + + // Font Awesome 6 + wp_enqueue_style( + 'fa6', + 'https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.2/css/all.min.css', + [], + null + ); + + // Dark mode overrides + wp_enqueue_style( + 'ps-dark-overrides', + get_stylesheet_directory_uri() . '/dark-overrides.css', + [], + null + ); +}); + +// Make our child theme the default fallback (prevents "twentytwentyfive" errors) +if (!defined('WP_DEFAULT_THEME')) { + define('WP_DEFAULT_THEME', 'ollie-child'); +} \ No newline at end of file diff --git a/wp-content/themes/postsecret/header.php b/wp-content/themes/postsecret/header.php new file mode 100644 index 0000000..50a7143 --- /dev/null +++ b/wp-content/themes/postsecret/header.php @@ -0,0 +1,12 @@ + +> + + + + + +> + + +
+ diff --git a/wp-content/themes/postsecret/inc/a11y.php b/wp-content/themes/postsecret/inc/a11y.php new file mode 100644 index 0000000..a26b821 --- /dev/null +++ b/wp-content/themes/postsecret/inc/a11y.php @@ -0,0 +1,16 @@ +' . esc_html__( 'Skip to content', 'postsecret' ) . ''; +} +add_action( 'wp_body_open', __NAMESPACE__ . '\\skip_to_content' ); diff --git a/wp-content/themes/postsecret/inc/routing.php b/wp-content/themes/postsecret/inc/routing.php new file mode 100644 index 0000000..d3ab96d --- /dev/null +++ b/wp-content/themes/postsecret/inc/routing.php @@ -0,0 +1,30 @@ + __( 'Secrets', 'postsecret' ), + 'singular_name' => __( 'Secret', 'postsecret' ), + ]; + + $args = [ + 'labels' => $labels, + 'public' => true, + 'has_archive' => true, + 'rewrite' => [ 'slug' => 'secrets' ], + 'supports' => [ 'title', 'editor', 'thumbnail', 'excerpt', 'tags' ], + 'show_in_rest' => true, + ]; + + register_post_type( 'secret', $args ); +} +add_action( 'init', __NAMESPACE__ . '\\register_secret_cpt' ); diff --git a/wp-content/themes/postsecret/inc/seo.php b/wp-content/themes/postsecret/inc/seo.php new file mode 100644 index 0000000..81b7bd5 --- /dev/null +++ b/wp-content/themes/postsecret/inc/seo.php @@ -0,0 +1,19 @@ +' . "\n"; + } +} +add_action( 'wp_head', __NAMESPACE__ . '\\meta_description' ); diff --git a/wp-content/themes/postsecret/index.php b/wp-content/themes/postsecret/index.php new file mode 100644 index 0000000..07edbb9 --- /dev/null +++ b/wp-content/themes/postsecret/index.php @@ -0,0 +1,29 @@ + + +
+ +

+ +
+ + { + const lightSrc = logo.getAttribute('data-light-src'); + const darkSrc = logo.getAttribute('data-dark-src'); + + if (mode === 'dark' && darkSrc) { + logo.src = darkSrc; + console.log('Set logo to dark version'); + } else if (lightSrc) { + logo.src = lightSrc; + console.log('Set logo to light version'); + } + }); + } + + function setMode(mode) { + console.log('Setting mode to:', mode); + + // Update DOM and storage + root.setAttribute('data-theme', mode); + localStorage.setItem(KEY, mode); + + console.log('DOM data-theme attribute set to:', root.getAttribute('data-theme')); + + // Force a style recalculation to ensure CSS variables are applied + document.body.offsetHeight; // This forces a reflow + + // Update logo after DOM changes + updateLogo(mode); + } + + // Update year in footer + function updateYear() { + const yearElements = document.querySelectorAll('.ps-year'); + const currentYear = new Date().getFullYear(); + yearElements.forEach(element => { + element.textContent = currentYear; + }); + } + + // Initialize logo and year on page load + document.addEventListener('DOMContentLoaded', () => { + // Update year immediately + updateYear(); + + // Small delay to ensure CSS is loaded for logo + setTimeout(() => { + const currentMode = root.getAttribute('data-theme'); + updateLogo(currentMode); + }, 100); + }); + + // Handle theme toggle clicks + document.addEventListener('click', (e) => { + const t = e.target.closest('[data-ps-theme-toggle]'); + if (!t) return; + + const current = root.getAttribute('data-theme'); + console.log('Current theme before toggle:', current); + + // Simple toggle: light ↔ dark + const next = current === 'dark' ? 'light' : 'dark'; + console.log('Next theme:', next); + + setMode(next); + + // Update button state + t.setAttribute('aria-pressed', next === 'dark' ? 'true' : 'false'); + }); + + // Remove system theme change handler - we only use explicit light/dark modes +})(); \ No newline at end of file diff --git a/wp-content/themes/postsecret/parts/card.php b/wp-content/themes/postsecret/parts/card.php new file mode 100644 index 0000000..d6e06bb --- /dev/null +++ b/wp-content/themes/postsecret/parts/card.php @@ -0,0 +1,28 @@ + +
> + +
+ + + +
+ +
+

+ +

+
+
+ +
+
+ ', ', ', '' ); ?> +
+
diff --git a/wp-content/themes/postsecret/parts/footer.html b/wp-content/themes/postsecret/parts/footer.html new file mode 100644 index 0000000..f75feea --- /dev/null +++ b/wp-content/themes/postsecret/parts/footer.html @@ -0,0 +1,156 @@ + +
+ + + + + + + + + + + + Some secrets are too heavy to carry alone. Click here for free anonymous support. + + + + + + + + + + + +
+ diff --git a/wp-content/themes/postsecret/parts/header.html b/wp-content/themes/postsecret/parts/header.html new file mode 100644 index 0000000..48fe78f --- /dev/null +++ b/wp-content/themes/postsecret/parts/header.html @@ -0,0 +1,73 @@ + +
+ + + + + + + + +
+ + + + + + + + + + + + + +
+ +
+ +
+ +
+ +
+ +
+ \ No newline at end of file diff --git a/wp-content/themes/postsecret/search.php b/wp-content/themes/postsecret/search.php new file mode 100644 index 0000000..8cf8da1 --- /dev/null +++ b/wp-content/themes/postsecret/search.php @@ -0,0 +1,40 @@ + + +
+

+ + +
+ +
+ + + +

+ +
+ + + +
+ +
> +

+
+ +
+
+ ', ', ', '' ); ?> +
+
+ +
+ + + + +
+ + +
+ +
+ + +

Browse the archive of secrets shared anonymously

+ +
+ + + +
+ +
+ +
+ +
+ +
+ + + +
+ +
+ + + +
+ + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + +
+ +

No secrets found

+ + + +

Try adjusting your search or browse all secrets.

+ +
+ + +
+ + +
+ + + \ No newline at end of file diff --git a/wp-content/themes/postsecret/templates/front-page.html b/wp-content/themes/postsecret/templates/front-page.html new file mode 100644 index 0000000..b7d3bcd --- /dev/null +++ b/wp-content/themes/postsecret/templates/front-page.html @@ -0,0 +1,118 @@ + + + +
+ + + + +
+ +
+ +
+ +

+ This is a living museum of anonymous postcards. Thousands of real secrets, no ads, just humanity. +

+ +
+ + + + + +
+ +
+ + + + +
+ + +

Latest Secrets

+ + + +
+ +
+ + + + + + + + + + + + + +
+ + +
+ + + \ No newline at end of file diff --git a/wp-content/themes/postsecret/templates/search.html b/wp-content/themes/postsecret/templates/search.html new file mode 100644 index 0000000..4100690 --- /dev/null +++ b/wp-content/themes/postsecret/templates/search.html @@ -0,0 +1,80 @@ + + + +
+ + + +
+ +

Search Results

+ + + +
+

+ + +

+
+ +
+ + + + +
+ + +
+ + + +
+ + + + + +
+ +
+ + + + + + + + + + + +
+ +

No secrets found

+ + + +

Sorry, no secrets match your search. Try different keywords or browse all secrets.

+ + + +
+ + + +
+ +
+ + +
+ + +
+ + + \ No newline at end of file diff --git a/wp-content/themes/postsecret/templates/single-secret.html b/wp-content/themes/postsecret/templates/single-secret.html new file mode 100644 index 0000000..694fa51 --- /dev/null +++ b/wp-content/themes/postsecret/templates/single-secret.html @@ -0,0 +1,188 @@ + + + +
+ + +
+ +

← Back to Archive

+ + + +
+ +
+ +
+ +
+ +
+ + + +
+ + + + +
+ +
+ + + + + + + + + + +
+ + + +
+ + +
+ + + +
+ +
+ + + +

More Secrets

+ + + +
+ + +
+ + + +
+ + +
+ +
+ + +
+ +
+ + +
+ + + \ No newline at end of file diff --git a/wp-content/themes/postsecret/theme.json b/wp-content/themes/postsecret/theme.json new file mode 100644 index 0000000..d19c1f9 --- /dev/null +++ b/wp-content/themes/postsecret/theme.json @@ -0,0 +1,38 @@ +{ + "$schema": "https://schemas.wp.org/wp/6.3/theme.json", + "version": 2, + "settings": { + "color": { + "palette": [ + { "slug": "bg", "color": "#ffffff", "name": "Background" }, + { "slug": "surface", "color": "#f6f8fa", "name": "Surface" }, + { "slug": "text", "color": "#0b1220", "name": "Text" }, + { "slug": "muted", "color": "#5d6b82", "name": "Muted" }, + { "slug": "accent", "color": "#0a84ff", "name": "Accent" }, + { "slug": "border", "color": "#e6eaf0", "name": "Border" } + ] + }, + "typography": { + "fontFamilies": [ + { "fontFamily": "Inter, system-ui, -apple-system, Segoe UI, Roboto, Helvetica, Arial, sans-serif", "slug": "inter", "name": "Inter" } + ], + "fontSizes": [ + { "slug": "sm", "size": "14px" }, + { "slug": "md", "size": "16px" }, + { "slug": "lg", "size": "20px" }, + { "slug": "xl", "size": "28px" } + ] + }, + "layout": { "contentSize": "750px", "wideSize": "1140px" } + }, + "styles": { + "color": { + "background": "var(--wp--preset--color--bg)", + "text": "var(--wp--preset--color--text)" + }, + "elements": { + "link": { "color": { "text": "var(--wp--preset--color--accent)" } } + }, + "typography": { "fontFamily": "var(--wp--preset--font-family--inter)" } + } +} \ No newline at end of file