move devices

This commit is contained in:
Matt Batchelder
2026-05-12 23:13:47 -04:00
parent 088e1bdd06
commit 474fe7dfdc
4 changed files with 247 additions and 5 deletions

View File

@@ -36,10 +36,13 @@ return <<<'ORIBI_SYNC_CONTENT'
<!-- wp:oribi/platform-row {"heading":"Cellular Connectivity","description":"Optional 4G/5G module for locations without Wi-Fi or wired internet. Combined with offline playback for total deployment flexibility.","cellularAnim":true} /--> <!-- wp:oribi/platform-row {"heading":"Cellular Connectivity","description":"Optional 4G/5G module for locations without Wi-Fi or wired internet. Combined with offline playback for total deployment flexibility.","cellularAnim":true} /-->
<!-- /wp:oribi/platform-section --> <!-- /wp:oribi/platform-section -->
<!-- wp:oribi/addon-section {"variant":"normal","heading":"Player Options","lead":"Every plan works with your existing screens. Need a player? We've got you covered.","columns":3} --> <!-- wp:oribi/device-section {"variant":"normal","heading":"Choose Your Player","lead":"Every plan works with your existing screens. Need a player? We've got you covered.","columns":2} -->
<!-- wp:oribi/addon-card {"iconType":"fontawesome","faIcon":"fas fa-microchip","title":"Standard Player","description":"A compact TV stick that plugs directly into your display's HDMI port. No cables, no clutter — just plug in and go. Great for single-screen setups.","tag":"$299 one-time"} /--> <!-- wp:oribi/device-card {"title":"Standard Player","description":"A compact HDMI stick that plugs directly into your display. No cables, no clutter — just plug in, connect to Wi-Fi, and go live. Ideal for single-screen setups in retail, hospitality, and small offices.","price":"$299","bestFor":"Best for single screens","specs":[{"key":"Processor","value":"Intel Celeron quad-core"},{"key":"RAM","value":"4GB LPDDR4"},{"key":"Storage","value":"64GB eMMC"},{"key":"Connectivity","value":"Wi-Fi 5"},{"key":"Output","value":"4K HDMI"},{"key":"OS","value":"Windows 11"}],"btnText":"Order Now","btnUrl":"/contact"} /-->
<!-- wp:oribi/addon-card {"iconType":"fontawesome","faIcon":"fas fa-server","title":"Pro Player","description":"A small TV box that sits behind your display and powers up to two screens in 4K. Built for high-traffic locations where reliability and picture quality matter.","tag":"$449 one-time"} /--> <!-- wp:oribi/device-card {"title":"Pro Player","description":"A compact mini PC that sits discreetly behind your display and can drive two screens simultaneously in 4K. Built for high-traffic venues, large-format displays, and multi-screen installations.","price":"$449","bestFor":"Best for dual screens \u0026 high traffic","specs":[{"key":"Processor","value":"Intel N100 quad-core"},{"key":"RAM","value":"8GB LPDDR5"},{"key":"Storage","value":"256GB SSD"},{"key":"Connectivity","value":"Wi-Fi 5 + Gigabit Ethernet"},{"key":"Output","value":"Dual 4K HDMI"},{"key":"OS","value":"Windows 11 Pro"}],"btnText":"Order Now","btnUrl":"/contact"} /-->
<!-- wp:oribi/addon-card {"iconType":"fontawesome","faIcon":"fas fa-plug","title":"Bring Your Own Hardware","description":"Or bring your own hardware — no setup fee on any plan.","tag":"No setup fee"} /--> <!-- /wp:oribi/device-section -->
<!-- wp:oribi/addon-section {"variant":"alt","heading":"Bring Your Own Hardware","lead":"Already have a compatible device? Use it — no setup fee on any plan.","columns":1} -->
<!-- wp:oribi/addon-card {"iconType":"fontawesome","faIcon":"fas fa-plug","title":"BYO Hardware","description":"OTS Signs runs on the Xibo platform, which supports Android 7.0+, Windows 10+, ChromeOS, Samsung Tizen, LG webOS, and Amazon Fire. If your existing hardware runs any of these platforms, it's likely compatible. No setup fee applies. Contact our team and we'll confirm within one business day.","tag":"No setup fee","btnText":"Check Compatibility","btnUrl":"/contact"} /-->
<!-- /wp:oribi/addon-section --> <!-- /wp:oribi/addon-section -->
<!-- wp:oribi/feature-section {"variant":"alt","heading":"Supported Player Platforms","lead":"OTS Signs runs on the Xibo player platform. If your existing hardware runs any of the following operating systems or platforms, it's likely compatible with your Command Center.","columns":3} --> <!-- wp:oribi/feature-section {"variant":"alt","heading":"Supported Player Platforms","lead":"OTS Signs runs on the Xibo player platform. If your existing hardware runs any of the following operating systems or platforms, it's likely compatible with your Command Center.","columns":3} -->

View File

@@ -1656,7 +1656,80 @@ p:last-child { margin-bottom: 0; }
font-size: .9rem; font-size: .9rem;
} }
/* ── 8d. Image Card ───────────────────────────────────────── */ /* ── 8d. Device Card ───────────────────────────────────────── */
.device-grid {
--cols: 2;
}
.oribi-card.device-card {
display: flex;
flex-direction: column;
border-top: 3px solid var(--color-primary);
background: var(--card-bg);
padding: 0;
overflow: hidden;
}
.device-card img,
.device-card .card-image {
width: 100%;
aspect-ratio: 4 / 3;
object-fit: cover;
display: block;
}
.device-card-body {
display: flex;
flex-direction: column;
flex: 1;
padding: 1.5rem;
}
.device-best-for {
display: inline-block;
font-size: var(--wp--preset--font-size--xs, .75rem);
background: var(--color-primary-lt);
color: var(--color-primary);
padding: .25rem .75rem;
border-radius: 999px;
margin-bottom: .75rem;
font-weight: 600;
}
.device-card h3 {
color: var(--color-heading);
margin-bottom: .5rem;
}
.device-card p {
font-size: .9rem;
margin-bottom: 1rem;
}
.device-specs {
display: grid;
grid-template-columns: 1fr 1fr;
gap: .25rem .75rem;
font-size: .85rem;
margin: 0 0 1.25rem;
padding: 0;
}
.device-specs dt {
color: var(--color-muted, #6b7280);
font-weight: 500;
}
.device-specs dd {
margin: 0;
font-weight: 600;
color: var(--color-heading);
}
.device-price {
font-size: 1.5rem;
font-weight: 700;
color: var(--color-primary);
margin-bottom: 1.25rem;
}
.device-card-btn {
display: block;
width: 100%;
text-align: center;
margin-top: auto;
}
/* ── 8e. Image Card ────────────────────────────────────────── */
.image-card { .image-card {
padding: 0; padding: 0;
overflow: hidden; overflow: hidden;

View File

@@ -1007,6 +1007,86 @@
save: function () { return null; } save: function () { return null; }
}); });
/* ── Device Card ──────────────────────────────────────────────────────── */
reg('oribi/device-card', {
title: 'Device Card',
icon: 'laptop',
category: 'oribi',
parent: ['oribi/device-section'],
supports: { html: false, reusable: false },
attributes: Object.assign({}, {
title: { type: 'string', default: '' },
description: { type: 'string', default: '' },
price: { type: 'string', default: '' },
bestFor: { type: 'string', default: '' },
specs: { type: 'array', default: [], items: { type: 'object' } },
btnText: { type: 'string', default: 'Order Now' },
btnUrl: { type: 'string', default: '/contact' },
}, CARD_IMAGE_ATTRS),
edit: function (props) {
var a = props.attributes, s = props.setAttributes;
var imgPrev = cardImagePreview(a);
function updateSpec(i, field, val) {
var next = (a.specs || []).slice();
next[i] = Object.assign({}, next[i], {});
next[i][field] = val;
s({ specs: next });
}
function addSpec() {
s({ specs: (a.specs || []).concat([{ key: '', value: '' }]) });
}
function removeSpec(i) {
var next = (a.specs || []).slice();
next.splice(i, 1);
s({ specs: next });
}
var specsRows = (a.specs || []).map(function (sp, i) {
return el('div', { key: i, style: { display: 'flex', gap: '8px', marginBottom: '6px' } },
el(TC, { label: 'Key', value: sp.key || '', onChange: function (v) { updateSpec(i, 'key', v); }, style: { flex: 1 } }),
el(TC, { label: 'Value', value: sp.value || '', onChange: function (v) { updateSpec(i, 'value', v); }, style: { flex: 1 } }),
el('button', { onClick: function () { removeSpec(i); }, style: { alignSelf: 'flex-end', marginBottom: '8px' }, className: 'button is-small is-destructive' }, '✕')
);
});
return el(Frag, null,
el(IC, null,
el(PB, { title: 'Card Content' },
el(TC, { label: 'Best For (pill)', value: a.bestFor || '', onChange: function (v) { s({ bestFor: v }); } }),
el(TC, { label: 'Price', value: a.price || '', onChange: function (v) { s({ price: v }); } }),
el(TC, { label: 'Button Text', value: a.btnText || '', onChange: function (v) { s({ btnText: v }); } }),
el(TC, { label: 'Button URL', value: a.btnUrl || '', onChange: function (v) { s({ btnUrl: v }); } })
),
el(PB, { title: 'Specs' },
specsRows,
el('button', { onClick: addSpec, className: 'button is-secondary', style: { marginTop: '4px' } }, '+ Add Spec')
),
cardImageControls(a, s)
),
el('div', { className: 'oribi-card device-card' },
imgPrev,
el('div', { className: 'device-card-body' },
a.bestFor ? el('span', { className: 'device-best-for' }, a.bestFor) : null,
el(RT, { tagName: 'h3', value: a.title, onChange: function (v) { s({ title: v }); }, placeholder: 'Device name...' }),
el(RT, { tagName: 'p', value: a.description, onChange: function (v) { s({ description: v }); }, placeholder: 'Short description...' }),
(a.specs || []).length ? el('dl', { className: 'device-specs' },
(a.specs || []).map(function (sp, i) {
return [
el('dt', { key: 'k' + i }, sp.key || ''),
el('dd', { key: 'v' + i }, sp.value || '')
];
})
) : null,
a.price ? el('div', { className: 'device-price' }, a.price) : null,
el('span', { className: 'wp-block-button__link device-card-btn' }, a.btnText || 'Order Now')
)
)
);
},
save: function () { return null; }
});
/* ── Image Card ───────────────────────────────────────────────────────── */ /* ── Image Card ───────────────────────────────────────────────────────── */
reg('oribi/image-card', { reg('oribi/image-card', {
title: 'Image Card', title: 'Image Card',
@@ -1586,6 +1666,17 @@
save: function () { return el(IB.Content); } save: function () { return el(IB.Content); }
}); });
/* DEVICE SECTION ───────────────────────────────────────────────────────── */
reg('oribi/device-section', {
title: 'Oribi Device Section',
icon: 'laptop',
category: 'oribi',
supports: { align: ['full'], html: false },
attributes: SECTION_ATTRS,
edit: createCardSectionEdit(['oribi/device-card'], [['oribi/device-card', {}]], 'Device Card'),
save: function () { return el(IB.Content); }
});
/* IMAGE SECTION ────────────────────────────────────────────────────────── */ /* IMAGE SECTION ────────────────────────────────────────────────────────── */
reg('oribi/image-section', { reg('oribi/image-section', {
title: 'Oribi Image Section', title: 'Oribi Image Section',

View File

@@ -455,6 +455,31 @@ add_action('init', function () {
'render_callback' => 'oribi_render_addon_card', 'render_callback' => 'oribi_render_addon_card',
]); ]);
/* Device Section (parent) */
register_block_type('oribi/device-section', [
'attributes' => oribi_card_section_attributes(2),
'supports' => $block_supports,
'render_callback' => 'oribi_render_device_section',
]);
/* Device Card (child) */
register_block_type('oribi/device-card', [
'attributes' => array_merge(
[
'title' => ['type' => 'string', 'default' => ''],
'description' => ['type' => 'string', 'default' => ''],
'price' => ['type' => 'string', 'default' => ''],
'bestFor' => ['type' => 'string', 'default' => ''],
'specs' => ['type' => 'array', 'default' => [], 'items' => ['type' => 'object']],
'btnText' => ['type' => 'string', 'default' => ''],
'btnUrl' => ['type' => 'string', 'default' => ''],
],
oribi_card_image_attributes()
),
'supports' => $block_supports,
'render_callback' => 'oribi_render_device_card',
]);
/* Image Section (parent) */ /* Image Section (parent) */
register_block_type('oribi/image-section', [ register_block_type('oribi/image-section', [
'attributes' => oribi_card_section_attributes(3), 'attributes' => oribi_card_section_attributes(3),
@@ -1655,6 +1680,56 @@ function oribi_render_addon_card($a)
return ob_get_clean(); return ob_get_clean();
} }
/* ── Device Section ────────────────────────────────────────────────────────── */
function oribi_render_device_section($a, $content)
{
return oribi_render_card_section($a, $content, 'grid device-grid', 2);
}
/* ── Device Card ───────────────────────────────────────────────────────────── */
function oribi_render_device_card($a)
{
$img = oribi_card_image_html($a);
$img_cls = $img['card_class'] ? ' ' . $img['card_class'] : '';
$specs = !empty($a['specs']) && is_array($a['specs']) ? $a['specs'] : [];
$price = !empty($a['price']) ? $a['price'] : '';
$bestFor = !empty($a['bestFor']) ? $a['bestFor'] : '';
$btnText = !empty($a['btnText']) ? $a['btnText'] : 'Order Now';
$btnUrl = !empty($a['btnUrl']) ? $a['btnUrl'] : '/contact';
ob_start(); ?>
<div class="oribi-card device-card<?php echo esc_attr($img_cls); ?>">
<?php if ($img['html']): echo $img['html']; endif; ?>
<div class="device-card-body">
<?php if ($bestFor): ?>
<span class="device-best-for"><?php echo esc_html($bestFor); ?></span>
<?php endif; ?>
<h3><?php echo wp_kses_post($a['title']); ?></h3>
<p><?php echo wp_kses_post($a['description']); ?></p>
<?php if ($specs): ?>
<dl class="device-specs">
<?php foreach ($specs as $spec):
$k = isset($spec['key']) ? $spec['key'] : '';
$v = isset($spec['value']) ? $spec['value'] : '';
if (!$k && !$v) continue; ?>
<dt><?php echo esc_html($k); ?></dt>
<dd><?php echo esc_html($v); ?></dd>
<?php endforeach; ?>
</dl>
<?php endif; ?>
<?php if ($price): ?>
<div class="device-price"><?php echo esc_html($price); ?></div>
<?php endif; ?>
<?php if ($btnText && $btnUrl): ?>
<a href="<?php echo esc_url($btnUrl); ?>" class="wp-block-button__link device-card-btn"><?php echo esc_html($btnText); ?></a>
<?php endif; ?>
</div>
</div>
<?php
return ob_get_clean();
}
/* ── Image Section ─────────────────────────────────────────────────────────── */ /* ── Image Section ─────────────────────────────────────────────────────────── */
function oribi_render_image_section($a, $content) function oribi_render_image_section($a, $content)
{ {