SECTION REDESIGN: - Killed standalone dashboard image (fake AI laptop, added nothing) - New gap-px grid (signature pattern 2) with border-l-2 accents (pattern 1) - Numbered anchors (01-04) as visual rhythm per brand guide - Wider container: max-w-7xl matches hero width PERSONA CHANGES: - Renamed 'Organisation' -> 'Programme Manager' - Reorder: Charity Manager, Programme Manager, Personal Fundraiser, Volunteer - Updated /for/organisations page content to match PHOTOGRAPHY (4 new images via gemini-3-pro-image-preview): - persona-charity-manager.jpg — hijabi woman at mosque office desk - persona-programme-manager.jpg — man at desk with campaign calendar - persona-fundraiser.jpg — woman on London park bench with phone - persona-volunteer.jpg — young man handing card at charity gala - All optimized: 2.7MB -> 342KB (87% reduction via sharp) - Consistent documentary candid style, 3:2 landscape, warm tones FOOTER: - 'Organisations' -> 'Programme Managers' in nav links
728 lines
26 KiB
PHP
728 lines
26 KiB
PHP
<?php
|
|
|
|
use Manza\CharityRight\Etc;
|
|
use Manza\CharityRight\Mappers\AppealMapper;
|
|
use Manza\CharityRight\Theme;
|
|
|
|
require_once get_stylesheet_directory() . "/vendor/autoload.php";
|
|
|
|
// List of files which will be included.
|
|
// You can append ! then a comma limited list of class names which must exist to that file.
|
|
$vlTailwind_includes = array(
|
|
'/setup.php', // Some theme setup functions
|
|
'/template-tags.php', // Custom template tags
|
|
'/pagination.php', // Pagination
|
|
'/sidebars.php', // Sidebars
|
|
'/scripts-and-styles.php', // enqueue scripts and styles
|
|
'/page-titles.php', // page title function
|
|
'/breadcrumbs.php', // breadcrumbs trail function
|
|
'/acf.php', // ACF settings
|
|
'/menus.php', // MENU inits
|
|
'/ajax.php', // Custom AJAX returning.
|
|
'/blocks.php', // The custom block registry.
|
|
'/cpt.php', // Custom post type declarations
|
|
'/blog-import.php', // Blog post importer. NOTE: Remove this once we're deployed live and happy.
|
|
'/dashboard.php', // Configure the WP dashboard.
|
|
'/images.php', // Configure media library images.
|
|
"/ajax-campaigns.php", // custom AJAX campaign loader
|
|
"/ajax-blogs.php", // custom AJAX blog loader
|
|
'/ramadan-challenge.php', // receiving ramadan challenge appeals.
|
|
'/team365.php', // receiving team365 appeals.
|
|
);
|
|
|
|
foreach ( $vlTailwind_includes as $file ) {
|
|
$filepath = locate_template( 'includes' . $file );
|
|
if ( ! $filepath ) {
|
|
trigger_error( "Error locating /includes{$file} for inclusion", E_USER_ERROR );
|
|
}
|
|
require_once $filepath;
|
|
}
|
|
|
|
function buildHeaderTree( array &$elements, $parentId = 0 )
|
|
{
|
|
$branch = array();
|
|
foreach ( $elements as &$element )
|
|
{
|
|
if ( $element->menu_item_parent == $parentId )
|
|
{
|
|
$children = buildHeaderTree( $elements, $element->ID );
|
|
if ( $children )
|
|
$element->wpse_children = $children;
|
|
|
|
$branch[$element->ID] = $element;
|
|
unset( $element );
|
|
}
|
|
}
|
|
return $branch;
|
|
}
|
|
|
|
function searchSocialMedia($name, $array) {
|
|
foreach ($array as $key => $val) {
|
|
if ($val['name'] === $name) {
|
|
return $key;
|
|
}
|
|
}
|
|
return null;
|
|
}
|
|
|
|
add_image_size( 'cr_card_thumb', 600, 350 , true);
|
|
add_image_size( 'cr_large_blog_thumb', 600, 480 , true);
|
|
|
|
// custom excerpt length
|
|
function wpdocs_custom_excerpt_length( $length ) {
|
|
return 30;
|
|
}
|
|
add_filter( 'excerpt_length', 'wpdocs_custom_excerpt_length', 999 );
|
|
|
|
function new_excerpt_more( $more ) {
|
|
return '...';
|
|
}
|
|
add_filter('excerpt_more', 'new_excerpt_more');
|
|
|
|
(new Theme)->run();
|
|
|
|
// Force asset URLs to use www to avoid cross-origin script/font blocking.
|
|
add_filter( 'script_loader_src', function( $src ) {
|
|
return str_replace( 'https://charityright.org.uk', 'https://www.charityright.org.uk', $src );
|
|
} );
|
|
add_filter( 'style_loader_src', function( $src ) {
|
|
return str_replace( 'https://charityright.org.uk', 'https://www.charityright.org.uk', $src );
|
|
} );
|
|
|
|
// Redirect function from author pages
|
|
add_action('template_redirect', 'my_custom_disable_author_page');
|
|
|
|
function my_custom_disable_author_page() {
|
|
global $wp_query;
|
|
|
|
if ( is_author() ) {
|
|
// Redirect to homepage, set status to 301 permenant redirect.
|
|
// Function defaults to 302 temporary redirect.
|
|
wp_redirect(get_option('home'), 301);
|
|
exit;
|
|
}
|
|
}
|
|
|
|
function itsme_disable_feed() {
|
|
wp_die( __( 'No feed available, please visit the <a href="'. esc_url( home_url( '/' ) ) .'">homepage</a>!' ) );
|
|
}
|
|
|
|
function disable_attachment_pages() {
|
|
if (is_attachment()) {
|
|
// Get the parent post ID
|
|
$parent_post_id = wp_get_post_parent_id(get_the_ID());
|
|
|
|
// Redirect to parent post if available, or homepage if not
|
|
if ($parent_post_id) {
|
|
wp_redirect(get_permalink($parent_post_id), 301);
|
|
} else {
|
|
wp_redirect(home_url(), 301);
|
|
}
|
|
exit;
|
|
}
|
|
}
|
|
add_action('template_redirect', 'disable_attachment_pages');
|
|
|
|
add_action('do_feed', 'itsme_disable_feed', 1);
|
|
add_action('do_feed_rdf', 'itsme_disable_feed', 1);
|
|
add_action('do_feed_rss', 'itsme_disable_feed', 1);
|
|
add_action('do_feed_rss2', 'itsme_disable_feed', 1);
|
|
add_action('do_feed_atom', 'itsme_disable_feed', 1);
|
|
add_action('do_feed_rss2_comments', 'itsme_disable_feed', 1);
|
|
add_action('do_feed_atom_comments', 'itsme_disable_feed', 1);
|
|
|
|
add_action('init', function() {
|
|
// Add rewrite rule for campaign with query parameter
|
|
add_rewrite_rule(
|
|
'campaigns/([^/]+)/?$',
|
|
'index.php?post_type=campaign&name=$matches[1]',
|
|
'top'
|
|
);
|
|
|
|
// Register your query vars
|
|
add_filter('query_vars', function($vars) {
|
|
$vars[] = 'p'; // Using 'p' instead of 'page'
|
|
return $vars;
|
|
});
|
|
});
|
|
|
|
// Prevent canonical redirect
|
|
add_filter('redirect_canonical', function($redirect_url, $requested_url) {
|
|
if (is_singular('campaign')) {
|
|
return false;
|
|
}
|
|
return $redirect_url;
|
|
}, 10, 2);
|
|
|
|
// donations
|
|
add_action('rest_api_init', function () {
|
|
register_rest_route('charityright/v1', '/donations', [
|
|
'methods' => 'GET',
|
|
'callback' => 'get_external_donations',
|
|
'permission_callback' => '__return_true', // Adjust permissions if necessary
|
|
]);
|
|
});
|
|
|
|
if (!function_exists('wp_money_format')) {
|
|
function wp_money_format($amount, $currency_symbol = '£', $decimal_places = 2, $thousands_separator = ',', $decimal_separator = '.') {
|
|
// Ensure the amount is a float
|
|
$amount = floatval($amount);
|
|
|
|
// Format the amount
|
|
$formatted_amount = number_format($amount, $decimal_places, $decimal_separator, $thousands_separator);
|
|
|
|
// Return with the currency symbol
|
|
return $currency_symbol . $formatted_amount;
|
|
}
|
|
}
|
|
|
|
function get_external_donations($request) {
|
|
// Define the appeal ID if necessary, otherwise, pass it from the request
|
|
$appeal_id = $request->get_param('appeal_id');
|
|
$page = $request->get_param('page') ?? 1;
|
|
$per_page = 5; // Define how many donations you want per page
|
|
|
|
// External API URL with the appeal_id
|
|
$url = trim(str_replace("donate", "", get_field("generic_donate_url", "options")), "/") . "/api/v1/appeal-donations/{$appeal_id}?page={$page}&per_page={$per_page}";
|
|
|
|
// Fetch donations from the external API
|
|
$response = wp_remote_get($url);
|
|
|
|
if (is_wp_error($response)) {
|
|
return new WP_REST_Response([
|
|
'message' => 'Failed to fetch donations from external API',
|
|
'error' => $response->get_error_message()
|
|
], 500);
|
|
}
|
|
|
|
$data = json_decode(wp_remote_retrieve_body($response), true);
|
|
|
|
if (!$data || !isset($data['data']) || !is_array($data['data'])) {
|
|
return new WP_REST_Response([
|
|
'message' => 'No donations found or invalid data format'
|
|
], 404);
|
|
}
|
|
|
|
|
|
$formattedDonations = array_map(function($donation) {
|
|
return [
|
|
'id' => $donation['id'],
|
|
'donor_name' => $donation['is_anonymous'] ? 'Anonymous' : $donation['donor_name'],
|
|
'initials' => strtoupper(substr($donation['donor_name'], 0, 1)), // Extract initials
|
|
'donated_at' => $donation['created_at'], // Pass the raw date, format on frontend
|
|
'amount' => $donation['amount'],
|
|
'donor_message' => $donation['donor_message'] ?? ''
|
|
];
|
|
}, $data['data']);
|
|
|
|
// Return formatted donations and pagination info
|
|
return new WP_REST_Response([
|
|
'donations' => $formattedDonations,
|
|
'pagination' => [
|
|
'current_page' => $data['current_page'],
|
|
'last_page' => $data['last_page'],
|
|
'total' => $data['total']
|
|
]
|
|
], 200);
|
|
}
|
|
|
|
add_action('rest_api_init', function () {
|
|
register_rest_route('custom/v1', '/delete-post', array(
|
|
'methods' => 'DELETE',
|
|
'callback' => 'handle_delete_post',
|
|
'permission_callback' => '__return_true',
|
|
'args' => array(
|
|
'slug' => array(
|
|
'required' => true,
|
|
'validate_callback' => function($param) {
|
|
return is_string($param); // Ensure slug is a string
|
|
}
|
|
),
|
|
'api_key' => array(
|
|
'required' => true
|
|
)
|
|
)
|
|
));
|
|
|
|
register_rest_route('custom/v1', '/update-campaign-preview', array(
|
|
'methods' => 'POST',
|
|
'callback' => 'handle_update_campaign_preview',
|
|
'permission_callback' => '__return_true',
|
|
'args' => array(
|
|
'slug' => array(
|
|
'required' => true,
|
|
'validate_callback' => function($param) {
|
|
return is_string($param); // Ensure slug is a string
|
|
}
|
|
),
|
|
'api_key' => array(
|
|
'required' => true
|
|
)
|
|
)
|
|
));
|
|
|
|
register_rest_route('custom/v1', '/update-strava-leaderboard', array(
|
|
'methods' => 'POST',
|
|
'callback' => 'handle_strava_leaderboard',
|
|
'permission_callback' => '__return_true',
|
|
'args' => array(
|
|
'api_key' => array(
|
|
'required' => true
|
|
),
|
|
'list' => array(
|
|
'required' => false, // Set to true if `list` is mandatory
|
|
'validate_callback' => function ($param, $request, $key) {
|
|
return is_array($param); // Ensure `list` is an array
|
|
}
|
|
)
|
|
)
|
|
));
|
|
});
|
|
|
|
function handle_delete_post($request) {
|
|
// Validate API key (you should store this securely)
|
|
$valid_api_key = 'AKDJSHF8932YRJDFKSAFH120';
|
|
|
|
if ($request['api_key'] !== $valid_api_key) {
|
|
return new WP_Error(
|
|
'invalid_api_key',
|
|
'Invalid API key provided.',
|
|
array('status' => 403)
|
|
);
|
|
}
|
|
|
|
// Get the slug from the request
|
|
$slug = $request['slug'];
|
|
|
|
// Retrieve the post by slug
|
|
$post = get_page_by_path($slug, OBJECT, 'campaign');
|
|
|
|
if (!$post) {
|
|
return new WP_Error(
|
|
'post_not_found',
|
|
'Post not found',
|
|
array('status' => 404)
|
|
);
|
|
}
|
|
|
|
// Attempt to delete the post
|
|
$deleted = wp_delete_post($post->ID, true);
|
|
|
|
if (!$deleted) {
|
|
return new WP_Error(
|
|
'delete_failed',
|
|
'Failed to delete post',
|
|
array('status' => 500)
|
|
);
|
|
}
|
|
|
|
return array(
|
|
'status' => 'success',
|
|
'message' => 'Post deleted successfully',
|
|
'deleted_slug' => $slug
|
|
);
|
|
}
|
|
|
|
if (!function_exists("number_shorten")) {
|
|
/**
|
|
* Shorten long numbers and attaches K, M, B, etc. accordingly
|
|
* @param int $n
|
|
* @param int $precision
|
|
*
|
|
* @return string
|
|
*/
|
|
function number_shorten( $n, $precision = 1 ) {
|
|
if ($n < 900) {
|
|
// 0 - 900
|
|
$n_format = number_format($n, $precision);
|
|
$suffix = '';
|
|
} else if ($n < 900000) {
|
|
// 0.9k-850k
|
|
$n_format = number_format($n / 1000, $precision);
|
|
$suffix = 'K';
|
|
} else if ($n < 900000000) {
|
|
// 0.9m-850m
|
|
$n_format = number_format($n / 1000000, $precision);
|
|
$suffix = 'M';
|
|
} else if ($n < 900000000000) {
|
|
// 0.9b-850b
|
|
$n_format = number_format($n / 1000000000, $precision);
|
|
$suffix = 'B';
|
|
} else {
|
|
// 0.9t+
|
|
$n_format = number_format($n / 1000000000000, $precision);
|
|
$suffix = 'T';
|
|
}
|
|
// Remove unecessary zeroes after decimal. "1.0" -> "1"; "1.00" -> "1"
|
|
// Intentionally does not affect partials, eg "1.50" -> "1.50"
|
|
if ( $precision > 0 ) {
|
|
$dotzero = '.' . str_repeat( '0', $precision );
|
|
$n_format = str_replace( $dotzero, '', $n_format );
|
|
}
|
|
return $n_format . $suffix;
|
|
}
|
|
}
|
|
|
|
function handle_update_campaign_preview ($request){
|
|
$existingPost = Etc::findPostByMeta('campaign-preview', 'appeal_id', $request['id']);
|
|
if ($existingPost) {
|
|
|
|
// Update the existing post
|
|
$postId = wp_update_post([
|
|
'ID' => $existingPost->ID,
|
|
'post_title' => $request['name'],
|
|
'post_name' => $request['slug'],
|
|
'post_content' => $request['story'],
|
|
'post_type' => 'campaign-preview'
|
|
]);
|
|
|
|
// Return the updated post object
|
|
$campaign_preview = get_post($postId);
|
|
} else {
|
|
// Create a new post
|
|
$postId = wp_insert_post([
|
|
'post_title' => $request['name'],
|
|
'post_name' => $request['slug'],
|
|
'post_content' => $request['story'],
|
|
'post_status' => 'publish',
|
|
'post_type' => 'campaign-preview'
|
|
]);
|
|
error_log($postId);
|
|
|
|
|
|
// Add the custom meta field for appeal_id
|
|
add_post_meta($postId, 'appeal_id', $request['id']);
|
|
|
|
// Return the newly created post object
|
|
$campaign_preview = get_post($postId);
|
|
}
|
|
$body = $request->get_body();
|
|
error_log(print_r(json_decode($body, true), true));
|
|
|
|
foreach (AppealMapper::toACF(json_decode($body, true)) as $fieldKey => $fieldValue) {
|
|
update_field($fieldKey, $fieldValue, $campaign_preview);
|
|
}
|
|
|
|
return $campaign_preview;
|
|
}
|
|
|
|
function handle_strava_leaderboard (WP_REST_Request $request){
|
|
delete_field('strava_leaderboard', 'option');
|
|
$list = $request->get_param('list');
|
|
|
|
if ($list) {
|
|
foreach ($list as $item) {
|
|
$row = array(
|
|
'athlete_name' => $item['name'],
|
|
'athlete_image' => $item['image'],
|
|
'distance' => $item['distance']
|
|
);
|
|
|
|
add_row('strava_leaderboard', $row, 'option');
|
|
}
|
|
} else {
|
|
error_log('No list received.');
|
|
}
|
|
}
|
|
|
|
|
|
function ajax_words_of_hope_action_submit() : void{
|
|
$email = $_REQUEST[ "email" ];
|
|
$name = $_REQUEST[ "name" ];
|
|
$school = isset($_REQUEST[ "school" ]) ? $_REQUEST[ "school" ] : null;
|
|
$form = $_REQUEST[ "form" ];
|
|
$address = $_REQUEST[ "address" ];
|
|
$message = $_REQUEST[ "message" ];
|
|
$us_marketing = isset($_REQUEST[ "us_marketing" ]) ? $_REQUEST[ "us_marketing" ] : null;
|
|
$uk_marketing = isset($_REQUEST[ "uk_marketing" ]) ? $_REQUEST[ "uk_marketing" ] : null;
|
|
$is_in_us = $_REQUEST[ "is_in_us" ];
|
|
|
|
|
|
$data = array(
|
|
'email' => $email,
|
|
'name' => $name,
|
|
'school' => $school,
|
|
'form' => $form,
|
|
'address' => $address,
|
|
'message' => $message,
|
|
'us_marketing' => $us_marketing !== "on" && $is_in_us,
|
|
'uk_marketing' => $uk_marketing == "on" && !$is_in_us,
|
|
);
|
|
|
|
// Set up the arguments for the HTTP request
|
|
$args = array(
|
|
'method' => 'POST',
|
|
'body' => json_encode($data),
|
|
'headers' => array(
|
|
'Content-Type' => 'application/json',
|
|
),
|
|
);
|
|
|
|
$base_url = trim(get_field('generic_donate_url', 'options') ?? 'http://crukv2.test/donate/', '/');
|
|
$wohURL = str_replace("/donate", "/api/word-of-hope/store", $base_url);
|
|
|
|
$response = wp_remote_post($wohURL, $args);
|
|
|
|
|
|
// Check for errors in the request
|
|
if (is_wp_error($response)) {
|
|
$error_message = $response->get_error_message();
|
|
error_log("Error making HTTP request: $error_message");
|
|
wp_send_json_error(array('message' => 'Error making HTTP request.'));
|
|
return;
|
|
}
|
|
|
|
// Check the response from the external API
|
|
$response_body = wp_remote_retrieve_body($response);
|
|
$response_code = wp_remote_retrieve_response_code($response);
|
|
|
|
error_log(print_r($response_body, true));
|
|
if ($response_code === 201) {
|
|
// Successfully submitted, return success response
|
|
wp_send_json_success(array('message' => 'Your message has been successfully submitted.'));
|
|
} else {
|
|
// API returned an error, return failure response
|
|
wp_send_json_error(array('message' => 'There was an error submitting your message.'));
|
|
}
|
|
|
|
}
|
|
|
|
add_action( "wp_ajax_words_of_hope_action", "ajax_words_of_hope_action_submit" );
|
|
add_action( "wp_ajax_nopriv_words_of_hope_action", "ajax_words_of_hope_action_submit" );
|
|
|
|
// === CR Live Data: Real-time donation updates ===
|
|
add_action('init', function() {
|
|
\Manza\CharityRight\Endpoints\CacheBust::register();
|
|
});
|
|
|
|
add_action('wp_enqueue_scripts', function() {
|
|
if (is_singular('campaign')) {
|
|
wp_enqueue_script('cr-live', get_template_directory_uri() . '/assets/js/cr-live.js', [], '1.0.0', true);
|
|
}
|
|
});
|
|
// === End CR Live Data ===
|
|
|
|
// menu-fix-bust: force cache bust for gute.js
|
|
add_filter('script_loader_src', function($src, $handle) {
|
|
if ($handle === 'vl_new_acf_blocks') {
|
|
$src = add_query_arg('bust', '20260218d', $src);
|
|
}
|
|
return $src;
|
|
}, 10, 2);
|
|
|
|
|
|
// === UTM Parameter Forwarding (2026-02-19) ===
|
|
add_action('wp_footer', function() {
|
|
?>
|
|
<script>
|
|
(function(){
|
|
var utmParams = ['utm_source','utm_medium','utm_campaign','utm_term','utm_content','utm_id'];
|
|
var stored = {};
|
|
|
|
// 1. Check URL for UTM params
|
|
var urlParams = new URLSearchParams(window.location.search);
|
|
var hasNew = false;
|
|
utmParams.forEach(function(p) {
|
|
var v = urlParams.get(p);
|
|
if (v) { stored[p] = v; hasNew = true; }
|
|
});
|
|
|
|
// 2. If new UTMs found, save to localStorage
|
|
if (hasNew) {
|
|
localStorage.setItem('cr_utm', JSON.stringify(stored));
|
|
localStorage.setItem('cr_utm_ts', Date.now());
|
|
} else {
|
|
// 3. Load from localStorage (expire after 30 days)
|
|
try {
|
|
var s = localStorage.getItem('cr_utm');
|
|
var ts = localStorage.getItem('cr_utm_ts');
|
|
if (s && ts && (Date.now() - ts) < 30*86400000) {
|
|
stored = JSON.parse(s);
|
|
}
|
|
} catch(e) {}
|
|
}
|
|
|
|
// 4. Nothing stored? Exit
|
|
if (!Object.keys(stored).length) return;
|
|
|
|
// 5. Append UTMs to all links pointing to app.charityright.org.uk
|
|
function tagLinks() {
|
|
var links = document.querySelectorAll('a[href*="app.charityright.org.uk"]');
|
|
links.forEach(function(a) {
|
|
try {
|
|
var url = new URL(a.href);
|
|
var changed = false;
|
|
Object.keys(stored).forEach(function(k) {
|
|
if (!url.searchParams.has(k)) {
|
|
url.searchParams.set(k, stored[k]);
|
|
changed = true;
|
|
}
|
|
});
|
|
if (changed) a.href = url.toString();
|
|
} catch(e) {}
|
|
});
|
|
}
|
|
|
|
// Run on load and observe DOM changes
|
|
tagLinks();
|
|
var observer = new MutationObserver(function() { tagLinks(); });
|
|
observer.observe(document.body, { childList: true, subtree: true });
|
|
})();
|
|
</script>
|
|
<?php
|
|
}, 99);
|
|
// === END UTM Parameter Forwarding ===
|
|
|
|
// Add padding offset class for fixed header on pages that need it
|
|
add_filter('body_class', function($classes) {
|
|
// Pages with their own header offset handling
|
|
$excluded_templates = ['page-ramadan.php', 'page-fidya-kaffarah.php'];
|
|
$template = get_page_template_slug();
|
|
$template_file = basename(get_page_template());
|
|
if (!in_array($template, $excluded_templates) && !in_array($template_file, $excluded_templates)) {
|
|
$classes[] = 'cr-header-offset';
|
|
}
|
|
return $classes;
|
|
});
|
|
|
|
// Add crossorigin="anonymous" to third-party scripts for better error reporting
|
|
add_filter('script_loader_tag', function($tag, $handle, $src) {
|
|
if (strpos($src, home_url()) !== false || strpos($src, '/wp-') === 0) {
|
|
return $tag; // Skip local scripts
|
|
}
|
|
if (strpos($tag, 'crossorigin') !== false) {
|
|
return $tag; // Already has crossorigin
|
|
}
|
|
return str_replace(' src=', ' crossorigin="anonymous" src=', $tag);
|
|
}, 10, 3);
|
|
|
|
|
|
// ══════════════════════════════════════════════════════
|
|
// IN-APP BROWSER FIXES — SITE-WIDE
|
|
// Fixes conversion-killing issues in Instagram, Facebook,
|
|
// TikTok, WhatsApp, Snapchat, Twitter, Threads in-app browsers
|
|
// ══════════════════════════════════════════════════════
|
|
add_action("wp_head", "cr_inapp_browser_fixes", 1);
|
|
function cr_inapp_browser_fixes() {
|
|
?>
|
|
<style id="cr-inapp-fixes">
|
|
/* Prevent auto-zoom on input focus — iOS zooms on <16px inputs */
|
|
@supports (-webkit-touch-callout: none) {
|
|
input, select, textarea { font-size: max(16px, 1em) !important; }
|
|
}
|
|
/* Safe area insets for notched devices */
|
|
body { padding-bottom: env(safe-area-inset-bottom, 0px); }
|
|
/* Prevent text inflation in WebViews */
|
|
html { -webkit-text-size-adjust: 100%; text-size-adjust: 100%; }
|
|
/* Fix rubber-banding scroll issues */
|
|
html { overscroll-behavior-y: none; }
|
|
/* 100vh fix — add dvh fallback globally */
|
|
.min-h-screen, [style*="100vh"] { min-height: 100vh; min-height: 100dvh; }
|
|
/* Minimum touch targets (WCAG 2.5.8) */
|
|
a, button, [role="button"], input[type="submit"], input[type="button"] { min-height: 44px; }
|
|
/* Fix Livewire/Alpine flicker in WebViews */
|
|
[x-cloak] { display: none !important; }
|
|
/* In-app browser banner */
|
|
#cr-inapp-banner {
|
|
display: none; position: fixed; bottom: 0; left: 0; right: 0; z-index: 999999;
|
|
padding: 14px 20px calc(14px + env(safe-area-inset-bottom, 0px));
|
|
background: rgba(26,26,46,.97); backdrop-filter: blur(12px); -webkit-backdrop-filter: blur(12px);
|
|
border-top: 1px solid rgba(228,34,129,.25);
|
|
text-align: center; font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
|
|
}
|
|
#cr-inapp-banner p {
|
|
color: #fff; font-size: 13px; font-weight: 600; margin: 0 0 10px; line-height: 1.3;
|
|
}
|
|
#cr-inapp-banner .cr-iab-open {
|
|
display: inline-block; padding: 11px 28px;
|
|
background: #E42281; color: #fff !important; font-size: 13px; font-weight: 700;
|
|
border-radius: 8px; text-decoration: none !important; min-height: 44px; line-height: 22px;
|
|
}
|
|
#cr-inapp-banner .cr-iab-dismiss {
|
|
display: block; margin: 10px auto 0; background: none; border: none;
|
|
color: rgba(255,255,255,.35); font-size: 11px; cursor: pointer; padding: 6px;
|
|
min-height: 30px;
|
|
}
|
|
</style>
|
|
<?php
|
|
}
|
|
|
|
add_action("wp_body_open", "cr_inapp_browser_banner", 1);
|
|
function cr_inapp_browser_banner() {
|
|
?>
|
|
<div id="cr-inapp-banner" role="complementary" aria-label="Open in browser">
|
|
<p>For the best experience, open in your browser</p>
|
|
<a id="cr-iab-open" class="cr-iab-open" href="#">Open in Safari ↗</a>
|
|
<button class="cr-iab-dismiss" id="cr-iab-dismiss" aria-label="Dismiss">Continue here</button>
|
|
</div>
|
|
<script>
|
|
(function(){
|
|
if(sessionStorage.getItem("cr_iab_off")) return;
|
|
var ua=navigator.userAgent||"";
|
|
var isInApp=false;
|
|
// Detect all major in-app browsers
|
|
if(/FBAN|FBAV/i.test(ua)) isInApp=true; // Facebook
|
|
if(/Instagram/i.test(ua)) isInApp=true; // Instagram
|
|
if(/TikTok|BytedanceWebview|musical_ly/i.test(ua)) isInApp=true; // TikTok
|
|
if(/Snapchat/i.test(ua)) isInApp=true; // Snapchat
|
|
if(/Twitter|Threads/i.test(ua)) isInApp=true; // Twitter/Threads
|
|
if(/Line\//i.test(ua)) isInApp=true; // LINE
|
|
if(/\bwv\b|WebView/i.test(ua)) isInApp=true; // Generic Android WebView
|
|
if(/LinkedIn/i.test(ua)) isInApp=true; // LinkedIn
|
|
if(/Pinterest/i.test(ua)) isInApp=true; // Pinterest
|
|
if(/Telegram/i.test(ua)) isInApp=true; // Telegram
|
|
// iOS: has iPhone/iPad but NOT Safari = in-app WebView
|
|
if(!isInApp&&/iPhone|iPad/.test(ua)&&!/Safari/i.test(ua)) isInApp=true;
|
|
if(!isInApp) return;
|
|
|
|
var b=document.getElementById("cr-inapp-banner");
|
|
var o=document.getElementById("cr-iab-open");
|
|
var d=document.getElementById("cr-iab-dismiss");
|
|
if(!b||!o) return;
|
|
|
|
var url=window.location.href;
|
|
if(/iPhone|iPad|iPod/i.test(ua)){
|
|
o.href=url; o.setAttribute("target","_blank"); o.textContent="Open in Safari ↗";
|
|
} else {
|
|
o.href="intent:"+url.replace(/^https?/,"")+"#Intent;scheme=https;end";
|
|
o.textContent="Open in Chrome ↗";
|
|
}
|
|
b.style.display="block";
|
|
|
|
if(d) d.addEventListener("click",function(){
|
|
b.style.display="none";
|
|
sessionStorage.setItem("cr_iab_off","1");
|
|
});
|
|
|
|
// CRITICAL: Intercept window.open calls and redirect to same-window
|
|
// This fixes ALL donate buttons site-wide that use window.open
|
|
var _origOpen = window.open;
|
|
window.open = function(url, target) {
|
|
if(url && typeof url === "string" && (url.indexOf("app.charityright.org.uk") > -1 || url.indexOf("/donate") > -1)) {
|
|
// In-app browser: redirect same-window instead of popup
|
|
window.location.href = url;
|
|
return null;
|
|
}
|
|
return _origOpen.apply(window, arguments);
|
|
};
|
|
})();
|
|
</script>
|
|
<?php
|
|
}
|
|
|
|
|
|
// ══════════════════════════════════════════════════════
|
|
// PREFETCH + PRECONNECT — Speed up checkout transition
|
|
// In-app browsers have cold DNS/TCP so this is critical
|
|
// ══════════════════════════════════════════════════════
|
|
add_action("wp_head", "cr_inapp_prefetch_hints", 2);
|
|
function cr_inapp_prefetch_hints() {
|
|
?>
|
|
<link rel="preconnect" href="https://app.charityright.org.uk" crossorigin>
|
|
<link rel="dns-prefetch" href="https://app.charityright.org.uk">
|
|
<link rel="preconnect" href="https://js.stripe.com" crossorigin>
|
|
<link rel="dns-prefetch" href="https://js.stripe.com">
|
|
<link rel="preconnect" href="https://fonts.bunny.net" crossorigin>
|
|
<?php
|
|
}
|