Skip to content

New landing page configuration - #4606

Merged
ildyria merged 30 commits into
masterfrom
new-landing
Aug 16, 2026
Merged

New landing page configuration#4606
ildyria merged 30 commits into
masterfrom
new-landing

Conversation

@ildyria

@ildyria ildyria commented Aug 12, 2026

Copy link
Copy Markdown
Member

Summary by CodeRabbit

  • New Features
    • Added four configurable landing-page layouts with hero positioning, animations, backgrounds, CTAs, login placement, about content, and live previews.
    • Added admin management for custom navigation links and featured photos or albums, including ordering and enable/disable controls.
    • Added seven social-media footer links and responsive social icon rendering.
    • Added self-hosted Libre Franklin and Vazirmatn font support.
  • Documentation
    • Added landing-page configuration guidance and localized interface text across supported languages.
  • Bug Fixes
    • Improved watermark availability detection and reset controls.

ildyria and others added 7 commits August 10, 2026 15:37
Rewrite spec/plan/tasks for the configurable landing page feature to
document all resolved architecture questions (Q-054-01..19) as full
Decision Cards per spec-guidelines/open-questions-format.md, instead of
loose prose.

Q-054-01..10 cover the original design decisions (layout architecture,
v8-only scope, SE gating, admin UI approach, dropped custom-CSS field,
Mod Welcome category absorption, automatic+manual featured content,
free-text link icons).

Q-054-11..19 are gaps found during review and fixed directly:
- landing_show_stats corrected to portfolio-only (was inconsistent)
- confirmed the Featured tab's Search-based picker already surfaces
  private content to admin sessions (may_administrate bypass)
- SE-gated dropdown options decided as badged-but-selectable, always
  showing the stored (not effective) config value
- documented the intentional FR-054-26/S-054-27 numbering gap
- clarified route registration needs both router/paths.ts and
  resources/js/v8/router/routes.ts
- pinned the Mod Welcome filter citation to SettingsController.php:83
- designed the previously unspecified Reorder endpoint contract from
  scratch (no precedent existed anywhere in this codebase)
- added missing icon length validation
- confirmed studio's secondary CTA label is deliberately fixed

open-questions.md rows updated to cross-reference the cards' option
lettering; roadmap.md and _current-session.md updated accordingly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@ildyria ildyria added the v8 label Aug 12, 2026
@coderabbitai

coderabbitai Bot commented Aug 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: a8a5410a-7a24-4977-90e5-ac14b3ffb64a

📥 Commits

Reviewing files that changed from the base of the PR and between ee14691 and 1cf1804.

📒 Files selected for processing (1)
  • app/Http/Requests/LandingFeaturedItem/PatchLandingFeaturedItemRequest.php
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/Http/Requests/LandingFeaturedItem/PatchLandingFeaturedItemRequest.php

Included review availability: Your plan includes up to 8 reviews per rolling hour; 6 remain after this review.


📝 Walkthrough

Walkthrough

This change adds a configurable v8 landing page with four layouts, configurable content, animations, links, featured items, administration, APIs, localization, self-hosted fonts, and regression tests.

Changes

Landing page foundation

Layer / File(s) Summary
Configuration, models, migrations, and resources
app/Enum/*, app/Models/*, database/migrations/*, app/Http/Resources/GalleryConfigs/*
Adds landing-page enums, configuration keys, persistence tables, ULID models, typed resources, featured-content resolution, and SE fallback handling.
Administrative APIs
app/Http/Controllers/Admin/*Landing*, app/Http/Requests/Landing*, routes/api_v2.php
Adds administrator-authorized CRUD and transactional full-list reorder endpoints for landing links and featured items.
Landing rendering
resources/js/v8/views/Landing.vue, resources/js/v8/views/landing/*, resources/js/v8/components/landing/*, resources/js/v8/composables/landing/*
Replaces the legacy landing view with Classic, Portfolio, Meridian, and Studio layouts, shared positioning, background, animation, reduced-motion, and featured-content behavior.
Administration interface
resources/js/v8/views/admin/LandingConfig.vue, resources/js/v8/components/forms/landing/*, resources/js/services/landing-*, resources/js/v8/router/*
Adds settings persistence, live previews, link management, featured-item curation, search, reordering, routing, and dashboard access.
Validation and regression coverage
tests/Feature_v2/Landing*, tests/Traits/*Landing*
Covers authorization, CRUD, built-in-link protection, reordering, automatic and manual featured content, defaults, and SE gating.

Supporting changes

Layer / File(s) Summary
Typography, social links, and shared UI
resources/sass/*, resources/fonts/*, resources/js/v8/components/footers/*, resources/js/v8/icons.ts, app/Http/Resources/GalleryConfigs/FooterConfig.php
Adds self-hosted Libre Franklin and Vazirmatn fonts, social-link configuration and icons, footer integration, and landing animation utilities.
Localization and documentation
lang/*, DESIGN.md, docs/specs/4-architecture/features/054-configurable-landing-page/*
Adds landing-page translations and feature documentation across supported locales.
Project support and regression updates
.gitignore, package.json, README.md, docs/specs/_current-session.md, tests/Unit/Actions/Db/*
Adds Playwright support, clarifies Docker key setup, records feature status, and updates database optimization expectations. The session document contains unresolved merge-conflict markers.

Estimated code review effort: 5 (Critical) | ~120 minutes

Merge Risk: 🟡 Moderate · up to 1cf18

The landing-page configuration changes still contain several concrete behavior and consistency issues, including partial reorder updates, an ineffective social-media visibility toggle, unvalidated form submissions, and misleading configuration choices. The PR is not merge-ready until these issues are fixed or explicitly accepted by the owners.

Poem

A rabbit hops through layouts bright,
With links and albums tucked just right.
New fonts bloom in every view,
Tests guard each path they hop through.
“Configurable landing,” I cheer—
“The burrow’s polished for the year!”

🚥 Pre-merge checks | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 16.82% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@codecov

codecov Bot commented Aug 15, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 91.66667% with 34 lines in your changes missing coverage. Please review.
✅ Project coverage is 91.10%. Comparing base (7fab02d) to head (1cf1804).

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@ildyria
ildyria marked this pull request as ready for review August 16, 2026 13:57
@ildyria
ildyria requested a review from a team as a code owner August 16, 2026 13:57
@ildyria

ildyria commented Aug 16, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai please review!

@coderabbitai

coderabbitai Bot commented Aug 16, 2026

Copy link
Copy Markdown
Contributor

@ildyria I will review pull request #4606.

⚠️ Action not completed

No files to review.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (2)
docs/specs/4-architecture/features/054-configurable-landing-page/tasks.md (1)

373-378: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Finish the Markdown task footer.

Line 346 already records php artisan test --filter=LangTest as the confirmed translation check, but Line 375 says it still needs confirmation. Remove or update this stale TODO. The file also places _Last updated: 2026-08-11_ at the top and ends without the required footer.

As per coding guidelines: Markdown files must end with --- followed by *Last updated: [date of the update]*.

Source: Coding guidelines

docs/specs/_current-session.md (1)

285-317: 📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win

Resolve the merge-conflict markers before merging.

Lines 285-317 contain <<<<<<< HEAD, =======, and >>>>>>> master. The block combines incompatible Feature 054 and Feature 053 next steps. Line 12 states that Feature 053 is intentionally absent from this branch. Keep the Feature 054/052 state and remove the obsolete conflict branch.

Source: Coding guidelines

🟡 Minor comments (21)
app/Http/Resources/Rights/ModulesRightsResource.php-172-182 (1)

172-182: 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win

Do not construct Watermarker for a capability check.

Line 182 resolves Watermarker, which executes its constructor. The constructor calls is_watermark_enabled() and logs an error when watermark_photo_id is empty. An empty photo ID is the expected first-time configuration state for this availability flag.

Expose the module check without constructing Watermarker, for example as a static capability method, then call that method directly. This prevents false error logs on each rights-resource response before the administrator selects a watermark photo.

Proposed fix
--- app/Image/Watermarker.php
- public function is_module_enabled(): bool
+ public static function is_module_enabled(): bool

- if (!$this->is_module_enabled()) {
+ if (!self::is_module_enabled()) {

--- app/Http/Resources/Rights/ModulesRightsResource.php
- return resolve(Watermarker::class)->is_module_enabled();
+ return Watermarker::is_module_enabled();
app/Http/Resources/GalleryConfigs/LandingPageResource.php-211-227 (1)

211-227: 🚀 Performance & Scalability | 🟡 Minor | ⚡ Quick win

Avoid per-item queries for featured content.

Manual photo resolution loads size_variants only, then the resource calls albums()->first() for every photo. Album resolution already eager-loads cover relations, but the resource queries each cover again. A landing page with several featured items creates avoidable database queries.

  • app/Http/Resources/GalleryConfigs/LandingPageResource.php#L211-L227: Eager-load each manual photo's album ID with size_variants.
  • app/Http/Resources/GalleryConfigs/LandingFeaturedContentResource.php#L38-L59: Read the loaded albums, cover, and min_privilege_cover relations instead of calling albums() and querying Photo again.
resources/js/v8/components/forms/landing/LandingLinkFormDialog.vue-7-7 (1)

7-7: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Connect the footer button to the form so native validation runs.

The required and type="url" constraints on Line 10 and Line 14 are only enforced when the form is submitted. The footer button sits outside the <form> and calls save() directly, so an empty label or a malformed URL is sent to the API. Give the form an id and make the button submit it.

🔧 Proposed fix
-			<form `@submit.prevent`="save">
+			<form id="landing-link-form" `@submit.prevent`="save">
 				<UButton
+					type="submit"
+					form="landing-link-form"
 					:label="link ? $t('landing_link.save') : $t('landing_link.create')"
 					color="primary"
 					variant="solid"
 					class="flex-1 justify-center font-bold"
 					:loading="isSaving"
-					`@click`="save"
 				/>

Also applies to: 42-49

resources/js/v8/views/admin/WatermarkPreview.vue-128-131 (1)

128-131: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Clickable <label> reset controls are not keyboard accessible. All sites attach a reset click handler to a <label> and reveal the reset hint only on hover. The element is not focusable, so keyboard users cannot reach or trigger the reset, and the hint stays hidden for keyboard and touch users. Replace each <label> with <button type="button"> and give it an aria-label that states the reset action.

  • resources/js/v8/views/admin/WatermarkPreview.vue#L128-L131: convert the horizontal shift label to a button; keep sliderX = 0 as the click handler.
  • resources/js/v8/views/admin/LandingConfig.vue#L165-L170: convert the CTA horizontal shift label to a button; keep sliderCtaX = 0 as the click handler.
  • resources/js/v8/views/admin/LandingConfig.vue#L197-L202: convert the CTA vertical shift label to a button; keep sliderCtaY = 0 as the click handler.

The vertical shift label in resources/js/v8/views/admin/WatermarkPreview.vue#L177-L180 needs the same change.

resources/js/v8/views/admin/LandingConfig.vue-46-51 (1)

46-51: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Pass disabled-key to the animation USelectMenu.

The layout menu at Line 26 sets :disabled-key="'disabled'", so SE-locked layouts cannot be selected. The animation menu marks SE-locked entries with disabled and renders an SE badge, but it does not set disabled-key. A user can click a locked animation. The setter at Line 875 then discards the value without any feedback.

🔧 Proposed fix
-										<USelectMenu v-model="selectedAnimation" :items="animationOptions" label-key="label" class="w-1/2">
+										<USelectMenu
+											v-model="selectedAnimation"
+											:items="animationOptions"
+											label-key="label"
+											:disabled-key="'disabled'"
+											class="w-1/2"
+										>
resources/js/v8/components/settings/ConfigGroup.vue-222-228 (1)

222-228: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Return undefined for missing or non-numeric integer-range bounds.

NumberField declares max?: number, so intRangeMax can return undefined. When config.type is int:1, intRangeMax passes NaN as max. intRangeMin also passes NaN for a non-numeric minimum. Use a shared parser that returns undefined for missing or non-finite segments, and keep the minimum fallback at 0.

app/Http/Requests/LandingLink/DestroyLandingLinkRequest.php-44-46 (1)

44-46: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use strict boolean checks for is_built_in.

  • app/Http/Requests/LandingLink/DestroyLandingLinkRequest.php#L44-L46: replace the condition with $this->landing_link->is_built_in === true.
  • app/Http/Requests/LandingLink/PatchLandingLinkRequest.php#L54-L56: replace the left condition with $this->landing_link->is_built_in === true.
  • app/Http/Requests/LandingLink/UpdateLandingLinkRequest.php#L54-L56: replace the condition with $this->landing_link->is_built_in === true.

As per coding guidelines, use strict comparison (===) and only booleans in if statements.

Source: Coding guidelines

tests/Feature_v2/LandingLink/LandingLinkReorderTest.php-80-88 (1)

80-88: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Test the unknown-ID validation path.

The request has two IDs while only one landing link exists. The endpoint can reject this request as an invalid complete list without checking nonexistent-id.

Create two landing links. Submit one valid ID and one unknown ID. Keep the submitted ID count equal to the persisted link count. Assert that both sort_order values remain unchanged.

Proposed test adjustment
-		$link1 = LandingLink::factory()->create(['sort_order' => 0]);
+		$link1 = LandingLink::factory()->create(['sort_order' => 1]);
+		$link2 = LandingLink::factory()->create(['sort_order' => 0]);

 		$response = $this->actingAs($this->admin)->patchJson('LandingLink/Reorder', [
 			'ids' => [$link1->id, 'nonexistent-id'],
 		]);
 		$this->assertUnprocessable($response);
+		$this->assertSame(1, $link1->fresh()->sort_order);
+		$this->assertSame(0, $link2->fresh()->sort_order);
DESIGN.md-18-28 (1)

18-28: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required update footer to both modified documentation files.

  • DESIGN.md#L18-L28: add an HR and *Last updated: August 16, 2026* at the end of the file.
  • README.md#L78-L78: add an HR and *Last updated: August 16, 2026* at the end of the file.

Source: Coding guidelines

app/Http/Resources/GalleryConfigs/FooterConfig.php-30-30 (1)

30-30: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Rename sm_pixelfeed_url to sm_pixelfed_url before release.

The migration and resource keys match, but the public API and TypeScript contract misspell the platform name. Update all related references together.

resources/js/v8/components/landing/LandingMeridianRail.vue-91-102 (1)

91-102: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Keep the no-intro reveal sequence synchronized with the rail duration. The rail duration is 0.6 seconds, but content begins after 0.13 or 0.28 seconds.

  • resources/js/v8/components/landing/LandingMeridianRail.vue#L91-L102: delay label and caption entrances until rail growth completes.
  • resources/js/v8/views/landing/LandingMeridian.vue#L113-L114,L184-L186: delay background and header reveal until rail growth completes.
resources/sass/app-v8.css-16-16 (1)

16-16: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use one unquoted Vazirmatn font-family identifier. Stylelint reports the same font-family-name-quotes violation in both locations.

  • resources/sass/app-v8.css#L16-L16: change "Vazirmatn" to Vazirmatn in the global stack.
  • resources/sass/fonts.css#L115-L176: change "Vazirmatn" to Vazirmatn in every added @font-face declaration.

Source: Linters/SAST tools

resources/js/v8/components/landing/LandingBackgroundField.vue-90-107 (1)

90-107: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Prevent stale photo resolutions.

Line 90 starts an untracked request. If requests overlap, an older response can assign its URL after a newer photo ID was entered or loaded. The preview can then show a different photo than value.

Track a request generation, or verify the response ID still matches value.value.trim(), before assigning resolvedUrl or loadError.

docs/specs/4-architecture/features/054-configurable-landing-page/plan.md-4-5 (1)

4-5: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Use one verified feature completion state. The plan says Draft and has an unrun Analysis Gate, while the specification says Completed and mixes Active Features with Completed Features.

  • docs/specs/4-architecture/features/054-configurable-landing-page/plan.md#L4-L5: set the verified current state and record the completed Analysis Gate result.
  • docs/specs/4-architecture/features/054-configurable-landing-page/spec.md#L5-L10: keep the status and roadmap entry consistent with the verified state, including the documentation deliverables.

Source: Coding guidelines

docs/specs/4-architecture/features/054-configurable-landing-page/plan.md-228-235 (1)

228-235: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Add the required document footer.

Line 235 is the end of this Markdown document. Add an hr line followed by *Last updated: 2026-08-11* after the backlog section.

As per coding guidelines: “At the bottom of documentation files, add an hr line followed by "Last updated: [date of the update]".”

Source: Coding guidelines

docs/specs/4-architecture/roadmap.md-128-128 (1)

128-128: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Correct the roadmap footer metadata.

Line 20 records Feature 054 as completed on 2026-08-11 under Completed Features. Line 128 says it was added to Active Features on 2026-08-10. Update the footer to describe the move to Completed Features and use the current completion date.

Source: Coding guidelines

docs/specs/4-architecture/features/054-configurable-landing-page/tasks.md-6-8 (1)

6-8: 📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

Order verification tasks before implementation tasks.

The checklist says “Stage tests before implementation,” but implementation tasks appear before their tests. For example, T-054-08 precedes T-054-12, and T-054-13 precedes T-054-19. Reorder each increment or split the entries so tests are listed first.

As per coding guidelines: tasks.md must “mirror the plan, orders tests before code.” Based on learnings: keep increments bite-sized and update the checklist as each verification completes.

Sources: Coding guidelines, Learnings

docs/specs/4-architecture/features/054-configurable-landing-page/tasks.md-25-25 (1)

25-25: 🗄️ Data Integrity & Integration | 🟡 Minor | ⚡ Quick win

Change the stale task note to 3–100. The migration and FR-054-09 define landing_featured_items_count as int:3:100; the Arabic and Japanese descriptions are already correct. Update docs/specs/4-architecture/features/054-configurable-landing-page/tasks.md#L25 and leave both locale descriptions unchanged.

lang/bg/all_settings.php-673-685 (1)

673-685: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use meridian in both landing-setting descriptions.

  • lang/bg/all_settings.php: replace minimal with meridian.
  • lang/no/all_settings.php: apply the same correction.
lang/el/all_settings.php-675-687 (1)

675-687: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Correct the landing-setting descriptions in both locale files.

  • List classic, portfolio, meridian, and studio for landing_layout; remove minimal.
  • State that landing_intro_screen_enabled applies to all four layouts.
  • Limit the hero text settings to portfolio.
  • State that the about settings apply to portfolio and studio.
  • State that landing_cta_text applies to classic, portfolio, and meridian.
lang/it/landing_config.php (1)

37-38: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Use a descriptive label for the CTA text setting. Replace the incomplete text label with Call-to-action text or the appropriate localized equivalent in each affected locale.

🧹 Nitpick comments (12)
resources/js/v8/views/admin/Settings.vue (1)

227-230: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Consider respecting prefers-reduced-motion.

Smooth scrolling can affect users who request reduced motion. Select the behavior from the media query.

♻️ Optional refactor
-		window.scrollTo({ top: 0, behavior: "smooth" });
+		const prefersReducedMotion = window.matchMedia("(prefers-reduced-motion: reduce)").matches;
+		window.scrollTo({ top: 0, behavior: prefersReducedMotion ? "auto" : "smooth" });
resources/js/v8/components/forms/settings/ColorField.vue (1)

147-160: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Normalize the hex text on commit, not on every keystroke.

handleHexInput writes the normalized value back into the bound input as soon as six digits are present. The displayed text changes under the caret, and the caret moves to the end. Editing a middle digit becomes difficult. Emit the value but leave hexInput as typed, and let revertHexInputIfInvalid normalize on blur.

Also make the emitted case consistent. handleChange emits newColor.hex in its original case. handleHexInput emits an uppercase value. The stored configuration value then differs by input path.

♻️ Proposed refactor
 function handleChange(newColor: BlossomColorPickerColor) {
 	val.value = newColor;
 	hexInput.value = newColor.hex.toUpperCase();
-	emits("filled", props.config.key, `${newColor.hex}`);
+	emits("filled", props.config.key, newColor.hex.toUpperCase());
 }
 
 function handleHexInput(value: string | number) {
 	const normalized = normalizeHexCandidate(String(value));
 	if (normalized === null) {
 		// Not a complete hex code yet - leave it uncommitted until the user finishes typing.
 		return;
 	}
-	hexInput.value = normalized;
 	val.value = hexToBlossomValue(normalized);
 	emits("filled", props.config.key, normalized);
 }
 
 function revertHexInputIfInvalid() {
-	if (normalizeHexCandidate(hexInput.value) === null) {
+	const normalized = normalizeHexCandidate(hexInput.value);
+	if (normalized === null) {
 		hexInput.value = resolveHex(props.config).toUpperCase();
+		return;
 	}
+	hexInput.value = normalized;
 }
tests/Feature_v2/LandingPageResource/LandingFeaturedItemsAutomaticTest.php (1)

77-89: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Assert the automatic-selection order.

This test checks only count and item type. An implementation that returns the oldest public albums would still pass.

Create albums with deterministic publication timestamps. Assert the expected IDs and order for the configured count.

package.json (1)

73-73: 🔒 Security & Privacy | 🔵 Trivial | ⚡ Quick win

Remove the production playwright dependency.

playwright is used only by development tooling. Remove the duplicate entry from dependencies at package.json:73.

resources/js/v8/components/footers/LandingFooter.vue (1)

14-17: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Extract the duplicated icon class list.

Both branches repeat the same link-class array. Move it to a single computed value and bind it in both places.

♻️ Suggested consolidation
+const socialLinkClass = computed(() => [
+	"inline-block hover:scale-150 transition-all ease-in-out duration-300 text-white text-2xl my-4 mx-5",
+	{ "opacity-50 hover:opacity-100": props.dimIconsUntilHover === true },
+]);

Then bind :link-class="socialLinkClass" in both branches.

Also applies to: 29-32

resources/js/v8/components/footers/SocialMediaLinks.vue (1)

2-93: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Replace the twelve repeated anchors with a v-for.

Every block differs only by URL field, icon name, and label. A single array of { url, icon, label } plus one v-for removes about 80 lines and prevents divergence when a new network is added.

♻️ Suggested structure
+const networks = computed(() =>
+	[
+		{ url: props.footerData.sm_facebook_url, icon: "social:facebook", label: "Facebook" },
+		{ url: props.footerData.sm_flickr_url, icon: "social:flickr", label: "Flickr" },
+		// ...remaining networks
+	].filter((n) => n.url !== ""),
+);
+	<a v-for="n in networks" :key="n.label" :href="n.url" :class="linkClass" target="_blank" rel="noopener" :aria-label="n.label">
+		<UIcon :name="n.icon" />
+	</a>
resources/js/v8/components/landing/LandingPortfolioFeatured.vue (1)

84-86: 🚀 Performance & Scalability | 🔵 Trivial | 💤 Low value

Use the debounced layout in onUpdated.

onUpdated runs on every re-render of this component and starts a full masonry pass each time. The resize path already uses debouncedLayout. Use the same function here to bound the work.

 onUpdated(() => {
-	runMasonryLayout();
+	debouncedLayout();
 });
resources/js/v8/views/landing/LandingClassic.vue (2)

141-169: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove the invalid translate declaration and the unused .light-delay rule.

Line 143 sets translate: translateY(-300px);. The CSS translate property accepts length or percentage components, not a translateY() function. Browsers drop the declaration. The 0% keyframe already sets transform: translateY(-300px), so the intent is covered.

.light-delay is declared in a scoped style block and is not applied by any element in this component. Scoped styles cannot reach other components, so the rule is dead.

🧹 Proposed cleanup
 .animate-landingAnimateDown {
 	opacity: 0;
-	translate: translateY(-300px);
 	animation-name: landingAnimateDown;
-
-.light-delay {
-	animation-delay: 0.5s !important;
-}

124-138: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Placement filtering and CTA fallback are duplicated across landing components. Each layout re-implements the same link placement filter and the same cta_text fallback. A shared composable (for example useLandingLinks) removes the duplication and keeps the placement rule in one place.

  • resources/js/v8/views/landing/LandingClassic.vue#L124-L138: replace the local navLinks and ctaText computed values with the shared composable.
  • resources/js/v8/views/landing/LandingPortfolio.vue#L110-L123: replace the identical navLinks and ctaText computed values with the same composable.
  • resources/js/v8/components/footers/LandingFooter.vue#L91-L91: obtain footerLinks from the shared placement filter instead of repeating the "footer" | "both" test.
resources/js/v8/views/landing/LandingPortfolio.vue (1)

130-157: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Merge the two preset-to-class switches.

heroEntranceClass and sectionRevealClass share four of five branches. Only parallax_scroll differs. One helper that maps the preset, with an optional override for parallax_scroll, removes the duplication.

resources/js/v8/composables/landing/useLandingAnimation.ts (1)

15-26: 🎯 Functional Correctness | 🔵 Trivial | ⚡ Quick win

Use useMediaQuery for reactive reduced-motion detection.

@vueuse/core 14.4.0 exports useMediaQuery. Use it for "(prefers-reduced-motion: reduce)" so isReducedMotion updates when the media query changes.

resources/js/v8/views/landing/LandingStudio.vue (1)

110-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Use promise chaining in this Vue component.

Replace async and await with .then(). The repository guideline requires this pattern in Vue3 files.

As per coding guidelines: “Do not use await async calls in Vue3, use .then() instead.”

Source: Coding guidelines


ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9d8caf39-3cb4-44ff-8bcf-b9d4a0a9a4d5

📥 Commits

Reviewing files that changed from the base of the PR and between 7fab02d and ee14691.

⛔ Files ignored due to path filters (17)
  • package-lock.json is excluded by !**/package-lock.json
  • resources/fonts/libre-franklin-v20-100.woff2 is excluded by !**/*.woff2
  • resources/fonts/libre-franklin-v20-200.woff2 is excluded by !**/*.woff2
  • resources/fonts/libre-franklin-v20-300.woff2 is excluded by !**/*.woff2
  • resources/fonts/libre-franklin-v20-400.woff2 is excluded by !**/*.woff2
  • resources/fonts/libre-franklin-v20-500.woff2 is excluded by !**/*.woff2
  • resources/fonts/libre-franklin-v20-600.woff2 is excluded by !**/*.woff2
  • resources/fonts/libre-franklin-v20-700.woff2 is excluded by !**/*.woff2
  • resources/fonts/libre-franklin-v20-900.woff2 is excluded by !**/*.woff2
  • resources/fonts/vazirmatn-100.woff2 is excluded by !**/*.woff2
  • resources/fonts/vazirmatn-200.woff2 is excluded by !**/*.woff2
  • resources/fonts/vazirmatn-300.woff2 is excluded by !**/*.woff2
  • resources/fonts/vazirmatn-400.woff2 is excluded by !**/*.woff2
  • resources/fonts/vazirmatn-500.woff2 is excluded by !**/*.woff2
  • resources/fonts/vazirmatn-600.woff2 is excluded by !**/*.woff2
  • resources/fonts/vazirmatn-700.woff2 is excluded by !**/*.woff2
  • resources/fonts/vazirmatn-900.woff2 is excluded by !**/*.woff2
📒 Files selected for processing (273)
  • .gitignore
  • DESIGN.md
  • README.md
  • app/Enum/LandingAnimationPreset.php
  • app/Enum/LandingCtaPosition.php
  • app/Enum/LandingFeaturedItemType.php
  • app/Enum/LandingFeaturedItemsMode.php
  • app/Enum/LandingLayoutType.php
  • app/Enum/LandingLinkPlacement.php
  • app/Enum/LandingLoginPosition.php
  • app/Enum/LandingTextPosition.php
  • app/Http/Controllers/Admin/LandingFeaturedItemController.php
  • app/Http/Controllers/Admin/LandingLinkController.php
  • app/Http/Controllers/Admin/SettingsController.php
  • app/Http/Requests/LandingFeaturedItem/DestroyLandingFeaturedItemRequest.php
  • app/Http/Requests/LandingFeaturedItem/IndexLandingFeaturedItemRequest.php
  • app/Http/Requests/LandingFeaturedItem/PatchLandingFeaturedItemRequest.php
  • app/Http/Requests/LandingFeaturedItem/ReorderLandingFeaturedItemRequest.php
  • app/Http/Requests/LandingFeaturedItem/ShowLandingFeaturedItemRequest.php
  • app/Http/Requests/LandingFeaturedItem/StoreLandingFeaturedItemRequest.php
  • app/Http/Requests/LandingFeaturedItem/UpdateLandingFeaturedItemRequest.php
  • app/Http/Requests/LandingLink/DestroyLandingLinkRequest.php
  • app/Http/Requests/LandingLink/IndexLandingLinkRequest.php
  • app/Http/Requests/LandingLink/PatchLandingLinkRequest.php
  • app/Http/Requests/LandingLink/ReorderLandingLinkRequest.php
  • app/Http/Requests/LandingLink/ShowLandingLinkRequest.php
  • app/Http/Requests/LandingLink/StoreLandingLinkRequest.php
  • app/Http/Requests/LandingLink/UpdateLandingLinkRequest.php
  • app/Http/Resources/Collections/LandingFeaturedItemCollection.php
  • app/Http/Resources/Collections/LandingLinkCollection.php
  • app/Http/Resources/GalleryConfigs/FooterConfig.php
  • app/Http/Resources/GalleryConfigs/LandingFeaturedContentResource.php
  • app/Http/Resources/GalleryConfigs/LandingLinkEmbedResource.php
  • app/Http/Resources/GalleryConfigs/LandingPageResource.php
  • app/Http/Resources/Models/LandingFeaturedItemResource.php
  • app/Http/Resources/Models/LandingLinkResource.php
  • app/Http/Resources/Rights/ModulesRightsResource.php
  • app/Image/Watermarker.php
  • app/Models/Configs.php
  • app/Models/LandingFeaturedItem.php
  • app/Models/LandingLink.php
  • app/Policies/AlbumQueryPolicy.php
  • app/Rules/LandingFeaturedItemExistsRule.php
  • database/factories/LandingFeaturedItemFactory.php
  • database/factories/LandingLinkFactory.php
  • database/migrations/2026_08_16_000000_add_contact_form_context_configs.php
  • database/migrations/2026_08_16_000001_add_social_media_urls_config.php
  • database/migrations/2026_08_16_000002_add_landing_page_configs.php
  • database/migrations/2026_08_16_000003_create_landing_links_table.php
  • database/migrations/2026_08_16_000004_create_landing_featured_items_table.php
  • docs/specs/4-architecture/features/054-configurable-landing-page/plan.md
  • docs/specs/4-architecture/features/054-configurable-landing-page/spec.md
  • docs/specs/4-architecture/features/054-configurable-landing-page/tasks.md
  • docs/specs/4-architecture/open-questions.md
  • docs/specs/4-architecture/roadmap.md
  • docs/specs/_current-session.md
  • lang/ar/all_settings.php
  • lang/ar/design-system.php
  • lang/ar/landing.php
  • lang/ar/landing_config.php
  • lang/ar/landing_featured_item.php
  • lang/ar/landing_link.php
  • lang/ar/watermark.php
  • lang/bg/all_settings.php
  • lang/bg/design-system.php
  • lang/bg/landing.php
  • lang/bg/landing_config.php
  • lang/bg/landing_featured_item.php
  • lang/bg/landing_link.php
  • lang/bg/watermark.php
  • lang/cz/all_settings.php
  • lang/cz/design-system.php
  • lang/cz/landing.php
  • lang/cz/landing_config.php
  • lang/cz/landing_featured_item.php
  • lang/cz/landing_link.php
  • lang/cz/watermark.php
  • lang/de/all_settings.php
  • lang/de/design-system.php
  • lang/de/landing.php
  • lang/de/landing_config.php
  • lang/de/landing_featured_item.php
  • lang/de/landing_link.php
  • lang/de/watermark.php
  • lang/el/all_settings.php
  • lang/el/design-system.php
  • lang/el/landing.php
  • lang/el/landing_config.php
  • lang/el/landing_featured_item.php
  • lang/el/landing_link.php
  • lang/el/watermark.php
  • lang/en/all_settings.php
  • lang/en/design-system.php
  • lang/en/landing.php
  • lang/en/landing_config.php
  • lang/en/landing_featured_item.php
  • lang/en/landing_link.php
  • lang/en/watermark.php
  • lang/es/all_settings.php
  • lang/es/design-system.php
  • lang/es/landing.php
  • lang/es/landing_config.php
  • lang/es/landing_featured_item.php
  • lang/es/landing_link.php
  • lang/es/watermark.php
  • lang/fa/all_settings.php
  • lang/fa/design-system.php
  • lang/fa/landing.php
  • lang/fa/landing_config.php
  • lang/fa/landing_featured_item.php
  • lang/fa/landing_link.php
  • lang/fa/watermark.php
  • lang/fr/all_settings.php
  • lang/fr/design-system.php
  • lang/fr/landing.php
  • lang/fr/landing_config.php
  • lang/fr/landing_featured_item.php
  • lang/fr/landing_link.php
  • lang/fr/watermark.php
  • lang/hu/all_settings.php
  • lang/hu/design-system.php
  • lang/hu/landing.php
  • lang/hu/landing_config.php
  • lang/hu/landing_featured_item.php
  • lang/hu/landing_link.php
  • lang/hu/watermark.php
  • lang/it/all_settings.php
  • lang/it/design-system.php
  • lang/it/landing.php
  • lang/it/landing_config.php
  • lang/it/landing_featured_item.php
  • lang/it/landing_link.php
  • lang/it/watermark.php
  • lang/ja/all_settings.php
  • lang/ja/design-system.php
  • lang/ja/landing.php
  • lang/ja/landing_config.php
  • lang/ja/landing_featured_item.php
  • lang/ja/landing_link.php
  • lang/ja/watermark.php
  • lang/nl/all_settings.php
  • lang/nl/design-system.php
  • lang/nl/landing.php
  • lang/nl/landing_config.php
  • lang/nl/landing_featured_item.php
  • lang/nl/landing_link.php
  • lang/nl/watermark.php
  • lang/no/all_settings.php
  • lang/no/design-system.php
  • lang/no/landing.php
  • lang/no/landing_config.php
  • lang/no/landing_featured_item.php
  • lang/no/landing_link.php
  • lang/no/watermark.php
  • lang/pl/all_settings.php
  • lang/pl/design-system.php
  • lang/pl/landing.php
  • lang/pl/landing_config.php
  • lang/pl/landing_featured_item.php
  • lang/pl/landing_link.php
  • lang/pl/watermark.php
  • lang/pt/all_settings.php
  • lang/pt/design-system.php
  • lang/pt/landing.php
  • lang/pt/landing_config.php
  • lang/pt/landing_featured_item.php
  • lang/pt/landing_link.php
  • lang/pt/watermark.php
  • lang/ru/all_settings.php
  • lang/ru/design-system.php
  • lang/ru/landing.php
  • lang/ru/landing_config.php
  • lang/ru/landing_featured_item.php
  • lang/ru/landing_link.php
  • lang/ru/watermark.php
  • lang/sk/all_settings.php
  • lang/sk/design-system.php
  • lang/sk/landing.php
  • lang/sk/landing_config.php
  • lang/sk/landing_featured_item.php
  • lang/sk/landing_link.php
  • lang/sk/watermark.php
  • lang/sv/all_settings.php
  • lang/sv/design-system.php
  • lang/sv/landing.php
  • lang/sv/landing_config.php
  • lang/sv/landing_featured_item.php
  • lang/sv/landing_link.php
  • lang/sv/watermark.php
  • lang/tr/all_settings.php
  • lang/tr/design-system.php
  • lang/tr/landing.php
  • lang/tr/landing_config.php
  • lang/tr/landing_featured_item.php
  • lang/tr/landing_link.php
  • lang/tr/watermark.php
  • lang/vi/all_settings.php
  • lang/vi/design-system.php
  • lang/vi/landing.php
  • lang/vi/landing_config.php
  • lang/vi/landing_featured_item.php
  • lang/vi/landing_link.php
  • lang/vi/watermark.php
  • lang/zh_CN/all_settings.php
  • lang/zh_CN/design-system.php
  • lang/zh_CN/landing.php
  • lang/zh_CN/landing_config.php
  • lang/zh_CN/landing_featured_item.php
  • lang/zh_CN/landing_link.php
  • lang/zh_CN/watermark.php
  • lang/zh_TW/all_settings.php
  • lang/zh_TW/design-system.php
  • lang/zh_TW/landing.php
  • lang/zh_TW/landing_config.php
  • lang/zh_TW/landing_featured_item.php
  • lang/zh_TW/landing_link.php
  • lang/zh_TW/watermark.php
  • package.json
  • resources/fonts/OFL-libre-franklin.txt
  • resources/fonts/OFL-vazirmatn.txt
  • resources/js/lychee.d.ts
  • resources/js/router/paths.ts
  • resources/js/services/landing-featured-item-service.ts
  • resources/js/services/landing-link-service.ts
  • resources/js/v8/components/footers/GalleryFooter.vue
  • resources/js/v8/components/footers/LandingFooter.vue
  • resources/js/v8/components/footers/SocialMediaLinks.vue
  • resources/js/v8/components/forms/auth/LoginForm.vue
  • resources/js/v8/components/forms/landing/LandingLinkFormDialog.vue
  • resources/js/v8/components/forms/settings/ColorField.vue
  • resources/js/v8/components/landing/LandingBackdrop.vue
  • resources/js/v8/components/landing/LandingBackgroundField.vue
  • resources/js/v8/components/landing/LandingBackgroundImages.vue
  • resources/js/v8/components/landing/LandingIntroScreen.vue
  • resources/js/v8/components/landing/LandingMeridianRail.vue
  • resources/js/v8/components/landing/LandingPortfolioFeatured.vue
  • resources/js/v8/components/settings/ConfigGroup.vue
  • resources/js/v8/components/settings/ConfirmSave.vue
  • resources/js/v8/composables/landing/useLandingAnimation.ts
  • resources/js/v8/composables/landing/useLandingBackgroundOrientation.ts
  • resources/js/v8/composables/landing/useLandingCtaPosition.ts
  • resources/js/v8/composables/landing/useLandingTextPosition.ts
  • resources/js/v8/composables/useAdminTiles.ts
  • resources/js/v8/composables/useScrollReveal.ts
  • resources/js/v8/icons.ts
  • resources/js/v8/router/routes.ts
  • resources/js/v8/views/Error.vue
  • resources/js/v8/views/Landing.vue
  • resources/js/v8/views/admin/DesignSystem.vue
  • resources/js/v8/views/admin/LandingConfig.vue
  • resources/js/v8/views/admin/Settings.vue
  • resources/js/v8/views/admin/WatermarkPreview.vue
  • resources/js/v8/views/landing/LandingClassic.vue
  • resources/js/v8/views/landing/LandingMeridian.vue
  • resources/js/v8/views/landing/LandingPortfolio.vue
  • resources/js/v8/views/landing/LandingStudio.vue
  • resources/sass/app-v8.css
  • resources/sass/fonts.css
  • routes/api_v2.php
  • routes/web_v2.php
  • tests/Feature_v2/LandingFeaturedItem/LandingFeaturedItemCrudTest.php
  • tests/Feature_v2/LandingFeaturedItem/LandingFeaturedItemReorderTest.php
  • tests/Feature_v2/LandingLink/LandingLinkBuiltInProtectionTest.php
  • tests/Feature_v2/LandingLink/LandingLinkCrudTest.php
  • tests/Feature_v2/LandingLink/LandingLinkReorderTest.php
  • tests/Feature_v2/LandingPageResource/LandingFeaturedItemsAutomaticTest.php
  • tests/Feature_v2/LandingPageResource/LandingFeaturedItemsManualTest.php
  • tests/Feature_v2/LandingPageResource/LandingPageContentTest.php
  • tests/Feature_v2/LandingPageResource/LandingPageSeGatingTest.php
  • tests/Traits/RequiresEmptyLandingFeaturedItems.php
  • tests/Traits/RequiresEmptyLandingLinks.php
  • tests/Unit/Actions/Db/OptimizeDbTest.php
  • tests/Unit/Actions/Db/OptimizeTablesTest.php
💤 Files with no reviewable changes (1)
  • resources/js/v8/views/admin/DesignSystem.vue

Included review availability: Your plan includes up to 8 reviews per rolling hour; 7 remain after this review.

Comment thread app/Http/Controllers/Admin/LandingLinkController.php
Comment thread resources/js/v8/components/footers/LandingFooter.vue
Comment thread resources/js/v8/components/landing/LandingPortfolioFeatured.vue
@ildyria
ildyria merged commit a203d75 into master Aug 16, 2026
51 checks passed
@ildyria
ildyria deleted the new-landing branch August 16, 2026 15:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant