feat: VivesPOS landing on Winter CMS 1.2 — theme + plugin + Dockerfile
Some checks are pending
Module sub-split / Sub-split (push) Waiting to run

- Base: wintercms/winter branch 1.2 (full framework)
- Theme vivespos: Canvas 7 + Bootstrap 5 CDN, custom CSS
- Layout: deferred GTM/GA4 tracking, JSON-LD SoftwareApplication
- Partials: hero (offline-first), features, modes (offline/nube toggle),
  screenshots, pricing (3 planes), comparison, FAQ, CTA
- Plugin VivesPOS.Site with ContactForm
- Dockerfile: PHP 8.2 Apache, port 80, healthcheck
- Added winter/wn-pages, blog, sitemap, seo plugins
- Active theme set to vivespos
This commit is contained in:
2026-08-21 19:29:00 -06:00
commit 1f72193a64
3266 changed files with 531480 additions and 0 deletions

View File

@@ -0,0 +1,46 @@
.report-container{margin-bottom:15px}
.report-container>ul{list-style:none;padding:0;margin-bottom:15px}
.report-container>ul .item{float:left;overflow:hidden;display:block}
.report-container>ul .item .content{margin:5px;position:relative}
.report-container>ul .item .content .drag-handle{width:14px;height:14px;position:absolute;left:15px;top:13px;opacity:0;filter:alpha(opacity=0);-webkit-transition:opacity 0.2s;transition:opacity 0.2s;font-size:14px;cursor:move}
.report-container>ul .item .content .drag-handle:before{font-family:"Font Awesome 6 Free";font-weight:900;-moz-osx-font-smoothing:grayscale;-webkit-font-smoothing:antialiased;font-style:normal;font-variant:normal;text-rendering:auto;content:"\f0c9";color:#b6b6b6}
.report-container>ul .item .content .widget-control{position:absolute;display:none;width:15px;height:15px;color:#b6b6b6;opacity:1;filter:alpha(opacity=100);outline:none}
.report-container>ul .item .content .widget-control:hover{color:#2da7c7}
.report-container>ul .item .content .report-widget h3{padding-left:0;-webkit-transition:padding 0.2s;transition:padding 0.2s}
.report-container>ul .item .content .edit-widget{text-indent:-10000em;right:15px;top:15px}
.report-container>ul .item .content .edit-widget:before{position:absolute;display:block;text-indent:0;left:0;width:15px;height:15px;font-size:16px}
.report-container>ul .item .content .edit-widget:hover:before{text-decoration:none}
.report-container>ul .item .content .edit-widget.inspector-open{display:block}
.report-container>ul .item .content .close{right:10px;top:15px}
.report-container>ul .item:hover .widget-control,
.report-container>ul .item.dragged .widget-control{display:block}
.report-container>ul .item:hover .report-widget h3,
.report-container>ul .item.dragged .report-widget h3{padding-left:18px}
.report-container>ul .item:hover .drag-handle,
.report-container>ul .item.dragged .drag-handle{opacity:1;filter:alpha(opacity=100)}
.report-container>ul .item.width-1{width:8.33%}
.report-container>ul .item.width-2{width:16.66%}
.report-container>ul .item.width-3{width:24.99%}
.report-container>ul .item.width-4{width:33.32%}
.report-container>ul .item.width-5{width:41.65%}
.report-container>ul .item.width-6{width:49.98%}
.report-container>ul .item.width-7{width:58.31%}
.report-container>ul .item.width-8{width:66.64%}
.report-container>ul .item.width-9{width:74.97%}
.report-container>ul .item.width-10{width:83.3%}
.report-container>ul .item.width-11{width:91.63%}
.report-container>ul .item.width-12{width:100%}
.report-container>ul .item.dragged{-webkit-transition-duration:0s !important;transition-duration:0s !important;-webkit-transform:translate3d(0,0,0) !important;transform:translate3d(0,0,0) !important;position:absolute;opacity:0.5;z-index:2000}
.report-container>ul .item.placeholder{display:none}
.report-container>ul .item.separator{width:100% !important}
.report-container>ul.add-delete .item .content .edit-widget{right:30px}
.report-container>ul.wrapped .item{width:100% !important}
.report-container .manage-widgets{display:inline-block;color:#bcc3c7;padding:10px 15px;font-size:14px;font-weight:400;border:1px dashed #bcc3c7;-webkit-border-radius:3px;-moz-border-radius:3px;border-radius:3px;margin-left:5px}
.report-container .manage-widgets i{margin-right:5px}
.report-container .manage-widgets:hover{text-decoration:none;background-color:#2da7c7;color:white;border:1px solid #2da7c7}
.report-container .dropdown.open .manage-widgets{text-decoration:none;background-color:#2da7c7;color:white;border:1px solid #2da7c7}
.report-container.isotope,
.report-container.isotope .isotope-item{-webkit-transition-duration:0.8s;transition-duration:0.8s}
.report-container.isotope{transition-property:height,width}
.report-container.isotope .isotope-item{transition-property:transform,opacity}
@media (max-width:768px){.report-container ul .item{width:100% !important}}

View File

@@ -0,0 +1,202 @@
/*
* Report container widget
*
* Data attributes:
* - data-control="report-container" - enables the report container plugin
*
* JavaScript API:
* $('#container').reportContainer()
*
* Dependancies:
* - Isotope (isotope.js)
*/
+function ($) { "use strict";
// REPORTCONTAINER CLASS DEFINITION
// ============================
var ReportContainer = function(element, options) {
this.options = options
this.$el= $(element)
this.$form = this.$el.closest('form')
this.$toolbar = $('[data-container-toolbar]', this.$form)
this.alias = $('[data-container-alias]', this.$form).val()
this.init();
}
ReportContainer.DEFAULTS = {
breakpoint: 768,
columns: 12
}
ReportContainer.prototype.init = function() {
var self = this
this.$el.isotope({
itemSelector: '.item',
resizable: false
})
$(window).resize($.proxy(this.updateWidth, this))
this.updateWidth()
if (!Modernizr.touchevents) {
this.$el.sortable({
vertical: false,
handle: '.drag-handle',
onDrop: function($item, container, _super) {
$item.removeClass('dragged')
$('body').removeClass('dragging')
self.updateSeparators()
self.redraw()
self.postSortOrders()
}
})
}
this.$el.on('hidden.oc.inspector', '[data-inspectable]', function() {
var values = $('[data-inspector-values]', this).val(),
parsedValues = JSON.parse(values),
li = $(this).closest('li').get(0)
self.$form.request(self.alias + '::onUpdateWidget', {
data: {
'fields': values,
'alias': $('[data-widget-alias]', $(this).closest('div.content')).val()
},
success: function(data) {
this.success(data).done(function() {
li.className = li.className.replace(/width\-[0-9]+/g, '')
$(li).addClass('width-'+parsedValues['ocWidgetWidth'])
$(li).toggleClass('new-line', parsedValues['ocWidgetNewRow'] == 1)
self.updateSeparators()
self.redraw()
})
}
})
})
this.$el.on('click', '.content > button.close', function() {
var $btn = $(this)
$.wn.confirm($.wn.lang.get('alert.widget_remove_confirm'), function() {
self.$form.request(self.alias + '::onRemoveWidget', {
data: {
'alias': $('[data-widget-alias]', $btn.closest('div.content')).val()
}
})
$btn.closest('li').remove()
self.redraw()
self.setSortOrders()
})
})
$(window).on('oc.reportWidgetAdded', function() {
self.redraw()
self.setSortOrders()
})
$(window).on('oc.reportWidgetRefresh', function() {
self.redraw()
})
window.setTimeout(function() {
self.updateWidth()
self.redraw()
}, 200)
this.setSortOrders()
}
ReportContainer.prototype.updateWidth = function() {
var width = this.$el.width(),
wrapped = width <= this.options.breakpoint,
columnWidth = wrapped ? width : width / this.options.columns
this.$el.isotope({
masonry: { columnWidth: columnWidth }
})
this.$el.toggleClass('wrapped', wrapped)
}
ReportContainer.prototype.redraw = function() {
this.$el
.isotope('reloadItems')
.isotope({ sortBy: 'original-order' })
var $items = $('li.item', this.$el)
$items.css({'width': '', 'height': ''})
$('> .dropdown', this.$toolbar).toggleClass('dropup', !!$items.length)
}
ReportContainer.prototype.setSortOrders = function() {
this.sortOrders = []
var self = this
$('[data-widget-order]', this.$el).each(function() {
self.sortOrders.push($(this).val())
})
}
ReportContainer.prototype.postSortOrders = function() {
var aliases = [],
self = this
$('[data-widget-alias]', this.$el).each(function() {
aliases.push($(this).val())
})
this.$form.request(self.alias + '::onSetWidgetOrders', {
data: {
'aliases': aliases.join(','),
'orders': self.sortOrders.join(',')
}
})
}
ReportContainer.prototype.updateSeparators = function() {
$('li.item.separator', this.$el).remove()
$('li.item.new-line', this.$el).each(function() {
$(this).before('<li class="item separator"></li>')
})
}
// REPORTCONTAINER PLUGIN DEFINITION
// ============================
var old = $.fn.reportContainer
$.fn.reportContainer = function(option) {
return this.each(function() {
var $this = $(this)
var data = $this.data('oc.reportContainer')
var options = $.extend({}, ReportContainer.DEFAULTS, $this.data(), typeof option == 'object' && option)
if (!data) $this.data('oc.reportContainer', (data = new ReportContainer(this, options)))
if (typeof option == 'string') data[option].call($this)
})
}
$.fn.reportContainer.Constructor = ReportContainer
// REPORTCONTAINER NO CONFLICT
// =================
$.fn.reportContainer.noConflict = function() {
$.fn.reportContainer = old
return this
}
// REPORTCONTAINER DATA-API
// ===============
$(document).render(function() {
$('[data-control="report-container"]').reportContainer()
})
}(window.jQuery);

View File

@@ -0,0 +1,196 @@
@import "../../../../assets/less/core/boot.less";
.report-container {
margin-bottom: 15px;
> ul {
list-style: none;
padding: 0;
margin-bottom: 15px;
.item {
float: left;
overflow: hidden;
display: block;
.content {
margin: 5px;
position: relative;
.drag-handle {
width: 14px;
height: 14px;
position: absolute;
left: 15px;
top: 13px;
.opacity(0);
.transition(opacity 0.2s);
font-size: 14px;
cursor: move;
&:before {
.icon(@bars);
color: @color-report-widget-control-inactive;
}
}
.widget-control {
position: absolute;
display: none;
width: 15px;
height: 15px;
color: @color-report-widget-control-inactive;
.opacity(1);
outline: none;
&:hover {
color: @link-color;
}
}
.report-widget h3 {
padding-left: 0;
.transition(padding 0.2s);
}
.edit-widget {
text-indent: -10000em;
right: 15px;
top: 15px;
&:before {
position: absolute;
display: block;
text-indent: 0;
left: 0;
width: 15px;
height: 15px;
font-size: 16px;
}
&:hover:before {
text-decoration: none;
}
&.inspector-open {
display: block;
}
}
.close {
right: 10px;
top: 15px;
}
}
&:hover, &.dragged {
.widget-control {
display: block;
}
.report-widget h3 {
padding-left: 18px;
}
.drag-handle {
.opacity(1);
}
}
&.width-1 {width: 8.33%;}
&.width-2 {width: 16.66%;}
&.width-3 {width: 24.99%;}
&.width-4 {width: 33.32%;}
&.width-5 {width: 41.65%;}
&.width-6 {width: 49.98%;}
&.width-7 {width: 58.31%;}
&.width-8 {width: 66.64%;}
&.width-9 {width: 74.97%;}
&.width-10 {width: 83.3%;}
&.width-11 {width: 91.63%;}
&.width-12 {width: 100%;}
&.dragged {
.transition-duration(0s) !important;
.translate3d(0, 0, 0) !important;
position: absolute;
opacity: 0.5;
z-index: 2000;
}
&.placeholder {
display: none;
}
&.separator {
width: 100% !important;
}
}
&.add-delete .item .content .edit-widget {
right: 30px;
}
&.wrapped {
.item {
width: 100% !important;
}
}
}
.manage-widgets {
display: inline-block;
color: #bcc3c7;
padding: 10px 15px;
font-size: 14px;
font-weight: 400;
border: 1px dashed #bcc3c7;
.border-radius(@border-radius-base);
margin-left: 5px;
i {
margin-right: 5px;
}
&:hover {
text-decoration: none;
background-color: @link-color;
color: white;
border: 1px solid @link-color;
}
}
.dropdown.open .manage-widgets {
text-decoration: none;
background-color: @link-color;
color: white;
border: 1px solid @link-color;
}
//
// Isotope CSS3 transitions
//
&.isotope,
&.isotope .isotope-item {
.transition-duration(0.8s);
}
&.isotope {
transition-property: height, width;
}
&.isotope .isotope-item {
transition-property: transform, opacity;
}
}
@media (max-width: @screen-sm) {
.report-container {
ul .item {
// The breakpoint should be defined in the CSS and JS,
// otherwise some widgets can't calculate the width properly,
// as the JS is applied too late.
width: 100% !important;
}
}
}

File diff suppressed because it is too large Load Diff

File diff suppressed because one or more lines are too long

View File

@@ -0,0 +1,16 @@
<div class="report-container">
<input type="hidden" value="<?= $this->alias ?>" data-container-alias />
<ul
id="<?= $this->getId('container-list') ?>"
class="<?= $this->canAddAndDelete ? 'add-delete' : null ?>"
data-control="report-container">
<?= $this->makePartial('widget_list') ?>
</ul>
<?php if ($this->canAddAndDelete): ?>
<div id="<?= $this->getId('container-toolbar') ?>" data-container-toolbar>
<?= $this->makePartial('widget_toolbar') ?>
</div>
<?php endif ?>
</div>

View File

@@ -0,0 +1,43 @@
<?= Form::open([
'data-request' => $this->getEventHandler('onAddWidget'),
'data-request-success' => "\$(this).trigger('close.oc.popup'); \$(window).trigger('oc.reportWidgetAdded')",
'data-popup-load-indicator' => 1
]) ?>
<div class="modal-header">
<button type="button" class="close" data-dismiss="popup">&times;</button>
<h4 class="modal-title"><?= e(trans('backend::lang.dashboard.add_widget')) ?></h4>
</div>
<div class="modal-body">
<div class="form-group">
<label><?= e(trans('backend::lang.dashboard.widget_label')) ?></label>
<select class="form-control custom-select" name="className" data-placeholder="<?= e(trans('backend::lang.form.select_placeholder')) ?>">
<option></option>
<?php foreach ($widgets as $className => $widgetInfo):?>
<option value="<?= e($className) ?>"><?= isset($widgetInfo['label']) ? e(trans($widgetInfo['label'])) : $className ?></option>
<?php endforeach ?>
</select>
</div>
<div class="form-group">
<label><?= e(trans('backend::lang.dashboard.widget_width')) ?></label>
<select class="form-control custom-select" name="size">
<?php foreach ($sizes as $size => $name):?>
<option value="<?= e($size) ?>" <?= $size == 12 ? 'selected' : null ?>><?= e($name) ?></option>
<?php endforeach ?>
</select>
</div>
</div>
<div class="modal-footer">
<button
type="submit"
class="btn btn-primary">
<?= e(trans('backend::lang.form.add')) ?>
</button>
<button
type="button"
class="btn btn-default"
data-dismiss="popup">
<?= e(trans('backend::lang.form.cancel')) ?>
</button>
</div>
<?= Form::close() ?>

View File

@@ -0,0 +1,33 @@
<?php if ($widget->property('ocWidgetNewRow')): ?>
<li class="item separator"></li>
<?php endif ?>
<li class="item <?= 'width-'.$widget->property('ocWidgetWidth') ?> <?= $widget->property('ocWidgetNewRow') ? 'new-line' : null ?>">
<div class="content">
<div class="drag-handle"></div>
<div id="<?= $widgetAlias ?>"><?= $widget->render() ?></div>
<a href="javascript:;"
class="wn-icon-cog widget-control edit-widget"
data-inspectable
data-inspector-title="<?= e(trans('backend::lang.dashboard.widget_inspector_title')) ?>"
data-inspector-description="<?= e(trans('backend::lang.dashboard.widget_inspector_description')) ?>"
data-inspector-config="<?= e($this->getWidgetPropertyConfig($widget)) ?>"
data-inspector-class="<?= get_class($widget) ?>"
data-inspector-offset="-3"
data-inspector-offset-x="-15"
data-inspector-placement="left"
data-inspector-fallback-placement="left">
<input type="hidden" name="widget_properties[]" data-inspector-values value="<?= e($this->getWidgetPropertyValues($widget)) ?>"/>
Edit
</a>
<?php if ($this->canAddAndDelete): ?>
<button type="button" class="close widget-control close-widget" data-dismiss="popover" aria-hidden="true">&times;</button>
<?php endif ?>
<input type="hidden" data-widget-alias name="widgetAliases[]" value="<?= $widgetAlias ?>"/>
<input type="hidden" data-widget-order name="widgetSortOrders[]" value="<?= $sortOrder ?>"/>
</div>
</li>

View File

@@ -0,0 +1,7 @@
<?php foreach ($widgets as $widgetAlias => $widgetInfo): ?>
<?= $this->makePartial('widget', [
'widgetAlias' => $widgetAlias,
'widget' => $widgetInfo['widget'],
'sortOrder' => $widgetInfo['sortOrder']
]) ?>
<?php endforeach ?>

View File

@@ -0,0 +1,48 @@
<div class="dropdown dropup">
<a
href="javascript:;"
class="manage-widgets"
data-toggle="dropdown">
<i class="icon-cogs"></i> <?= e(trans('backend::lang.dashboard.manage_widgets')) ?>
</a>
<ul class="dropdown-menu" role="menu" data-dropdown-title="Manage dashboard">
<li role="presentation">
<a
role="menuitem"
href="javascript:;"
data-control="popup"
data-handler="<?= $this->getEventHandler('onLoadAddPopup') ?>"
class="wn-icon-plus"
tabindex="-1">
<?= e(trans('backend::lang.dashboard.add_widget')) ?>
</a>
</li>
<li role="separator" class="divider"></li>
<?php if (\BackendAuth::getUser()->hasAccess('backend.manage_default_dashboard')): ?>
<li role="presentation">
<a
role="menuitem"
href="javascript:;"
class="wn-icon-floppy-o"
data-request="<?= $this->getEventHandler('onMakeLayoutDefault') ?>"
data-request-confirm="<?= e(trans('backend::lang.dashboard.make_default_confirm')) ?>"
tabindex="-1">
<?= e(trans('backend::lang.dashboard.make_default')) ?>
</a>
</li>
<?php endif; ?>
<li role="presentation">
<a
role="menuitem"
href="javascript:;"
data-request-success="$(window).trigger('oc.reportWidgetRefresh')"
data-request="<?= $this->getEventHandler('onResetWidgets') ?>"
data-request-confirm="<?= e(trans('backend::lang.dashboard.reset_layout_confirm')) ?>"
class="wn-icon-repeat"
tabindex="-1">
<?= e(trans('backend::lang.dashboard.reset_layout')) ?>
</a>
</li>
</ul>
</div>