November 2008 in the Life of Ben (Blog)
Smart Headers & HTML5 (30th November 2008)
This comparison is for HTMLWG, to inform further changes to the header association features for data tables in HTML5. The idea arose during TPAC 2008. It is tracked as Action 85 in HTMLWG.
Quick Reference
That’s All, Folks!
This comparison has influenced HTML5:
Ben's research was instrumental to the changes made here; his research probably had more of an effect on the spec than all the other discussions put together. I cannot emphasise enough how much more important objective analysis and logical argumentation is compared to opinions and assertions.
Ian Hickson
Final update was on 20th December 2008.
Feedback Finished
Feedback was welcomed until 15th December 2008. I extended this to 20th December 2008.
- E-mail me directly.
- Reply to the thread on Public-HTML, if you’re in HTMLWG.
- Use the
#whatwg
IRC channel or any other channel logged by Krijn Hoetmer. (If you direct a line at BenMillard, I’ll read that day of logs.) - E-mail me a link if it’s in a blog entry, forum topic or other mailing list.
Spread the word to any other lists, websites or individuals you think are relevant.
Introduction
Smart Headers is compared with HTML5. James Graham has built a prototype for both.
- Smart Headers prototype description by James Graham, or Smart Headers for convenience.
- Forming relationships between data cells and header cells in HTML5, or HTML5 for convenience.
HTML4 is not included due to ambiguities which become significant when assessing exactly what happens in genuine data tables.
Similarities
3 Mechanisms Applied
- A data cell with the
headers
attribute gets each header cell with correspondingid
attribute associated with it. <th scope>
is associated according to which of the 4 supported values was used:row
- associates rightwards, recognising
rowspan
. rowgroup
- associates rightwards and downwards to the end of that table section.
col
- associates downwards, recognising
colspan
. colgroup
- associates rightwards and downwards to the end of that column group.
- Plain
<th>
(or<th scope>
with an unsupported value) is automatically associated downwards or rightwards, depending on its position in the table.
Data Cells without Header Cells
- If there are no table header cells, clearly no associations will be made.
- Step 1 in Smart Headers says “If this returns any headers”, so none may be returned.
- Step 3.1 in Smart Headers may “[…] return an empty
cell_list
[…]” - HTML5 says: “Each data cell can be assigned zero or more header cells.”
Table Header Cells are <th>
isHeading
in Smart Headers only enables<th>
elements to be applied as header cells.- Smart Headers does not treat
<td scope>
as a header cell. - Processing Model in HTML5 says: “Header cells correspond to
th
elements.” <td headers>
is present in HTML5 but must only point to<th>
.
Regular Associations are Header Cell → Data Cells
- Both algorithms start at a header cell and run across all data cells until reaching the last data cell it applies to in their
scope
and automatic modes.
Optimised for Regular Associations
A regular data table has each header cell directly above or to the left of all the data cells it must be associated with. Smart Headers and HTML5 are optimised for this case by making associations automatically, without requiring scope
or headers
+id
. Specifically:
- Smart Headers has “the smart span algorithm” as a list of substeps.
- HTML5 has “the auto state” in Step 1.5.
Irregular Associations are Header Cells ← Data Cell
Upon finding a data cell with the headers
attribute, both algorithms will search for the corresponding header cells:
- Split the
headers
attribute value into its constituent tokens. - For each token, both algorithms scan the document (via
getElementById
) for the first element with matchingid
. - For each token, search for a header cell with matching
id
in that table. - If the element is a
<th>
in the current<table>
, associate it with the data cell.
Irregular Associations via headers
+id
An irregular data table has one or more header cells in a position which is not directly above or to the left of all the data cells it must be associated with. Smart Headers and HTML5 support these cases but require headers
+id
. Specifically:
- Smart Headers has “the basic algorithm”.
- HTML5 has Step 2, specifically Step 2.2 onwards.
Incremental Association
James Graham: “In principle I believe either algorithm could be written to run incrementally.”
Arbitrary Levels of Header Cells
- The number of table header cells which may associate with a data cell by any and all means is unlimited in Smart Headers:
- The number of adjacent
<th>
elements is unlimited in Smart Headers: - The number of tokens in the
headers
attribute is unlimited in Smart Headers. - There is no limit on how many
<th scope>
may cover a data cell.
- The number of adjacent
- HTML5 says: “Each data cell can be assigned zero or more header cells.”
Specialist Markup Takes Precendence
- Smart Headers has The Basic Algorithm, where the first step checks for
headers
+id
. If it finds any corresponding header cells, they are used and no further associations take place for that cell. - Smart Headers has The Smart Span Algorithm, where step 2 determines the value of
scope
. If it is one of the 4 supported values, it uses those associations and does not use the automatic association mode. - HTML5 step 1.5 specifies that
<th scope="foo">
will associate with everything covered by foo, avoiding the automatic association steps. - HTML5 step 2.2.2 lets
headers
+id
associate an arbitrary data cell to arbitrary header cells, regardless of other headers’ positions and spans.
Differently Spanned Header Cells (Adjacent or Distant)
- Smart Headers has The Get Cells From Axis Algorithm, where step 3.4.2 is unchanged by the
data_cell_found
flag:- Headers of different height in the same row associate.
- Headers of different width in the same column associate.
- HTML5 step 1.5 specifies that
<th scope="foo">
will associate for the entirety of foo, where foo is one of the 4 defined states, regardless of other headers and their spans within foo. - Smart headers has The Smart Span Algorithm, where step 3 gives the same priority to supported
scope
states, same as the previous bullet point.
Data Cells Spanning Different Spans of Header Cells
Both HTML5 and Smart Headers use 4.9.13.1 Forming a table to determine which slots in a table each cell covers, including spanned cells. As such, each header cell is associated with all cells that cover one or more slots in the area that header cell applies to.
- Discussion of timelines, including Ferrari road car timeline with expected results, covers tables with muliple levels of complex spans on both axes which are nonetheless regular in their positioning of header cells relative to the corresponding data cells.
Differences
Header Cells for Header Cells
- Smart Headers has The Get Cells From Axis Algorithm, where Step 3.4 and 3.4.2 associate header cells with header cells when they lay in particular positions with particular spans.
- Smart Headers has The Algorithm to Extract Table Headers From a Headers Attribute, where Step 3 lets a matching
<th id>
act as a header cell for the<th headers>
which pointed to it. - HTML5 says “Each data cell can be assigned zero or more header cells.” It does not say “Each data cell or header cell [...].” As such, header cells cannot be associated with header cells in HTML5. Header cells can “leapfrog” each other to continue associating with data cells, though.
Header Cells Blocking Header Cells
- Smart Headers blocks the current header cell from applying any further when it reaches a header cell of equal size in line with it on the same axis when there are one or more data cells between them.
- HTML5 blocks the current header cell from applying any further when it reaches a header cell of equal size in line with it on the same axis, with zero or more data data cells between them.
Equally Spanned Header Cells, Adjacent
- Smart Headers has The Get Cells From Axis Algorithm, where steps 3.4 and 3.4.1 run with the
data_cell_found
flag set tofalse
:- Adjacent headers of equal height in the same row associate.
- Adjacent headers of equal width in the same column associate.
- Step 1.5.5 in HTML5 stops the current header associating any further when a header of equal height is found in the same row, whether or not they are adjacent.
- Step 1.5.10 in HTML5 stops the current header associating any further when a header of equal width is found in the same column, whether or not they are adjacent.
Equally Spanned Header Cells, Data Cells Between
- Smart Headers has The Get Cells From Axis Algorithm, where steps 3.4 and 3.4.1 are run with the
data_cell_found
flag set totrue
:- A header of equal height to the current header cell will stop the current header cell from applying any further along the row if there were data cells between them.
- A header of equal width to the current header cell will stop the current header cell from applying any further down the column if there were data cells between them.
- Step 1.5.5 in HTML5 stops the current header associating any further when a header of equal height is found in the same row, whether or not they are adjacent.
- Step 1.5.10 in HTML5 stops the current header associating any further when a header of equal width is found in the same column, whether or not they are adjacent.
Empty Cells
- Smart Headers treats empty cells the same as any other.
- HTML5 defines emptiness for data cells but not for header cells.
- HTML5 says: “User agents may remove empty data cells when analyzing data in a table.”
Broken Tables are Unsupported
Tables with incorrect semantics inevitably lead to incorrect results.
- Tables made in word processors where column widths don’t line up create arbitrary spans in the table. Data cells end up in columns whose header cells should not apply to them. This is not supported.
- Tables which use
<td>
for header cells do not provide the necessary semantics. This is unsupported, even for simple tables.
Ben’s Advice for HTML5
Both algorithms reflect extensive feedback, research and testing. Each makes design choices which are not universally agreed on. Below are my suggested changes for HTML5. They are informed by:
- Research I’ve done during 2007 and 2008.
- Other people’s research which I’ve seen.
- My experience in professional auditing and retrofitting of accessibility to websites.
- Feedback I’ve read on forums, mailing lists and IRC.
- The common design patterns I’ve noticed on the web and in print during my life.
Header Cells for Header Cells
Tables can have multiple levels of header cells for columns or rows. Sometimes, the higher level of header cell is the only way to disambiguate the lower level of header cell when moving along the lower level of header cells.
#whatwg
discussion & examples with Hixie, September 2008.- E-mail summarising use cases, follow-up with more use cases.
- Multiple levels of header cell are already associated with data cells.
<td>
with Header Cell Semantics
Let <td>
act the same as <th>
when given header cell semantics via the scope
or headers
+id
features.
- In my data tables research from 2007, I found 18% of tables gave
<td>
header cell semantics in a way which seemed it would work. - Row header cells which have a column header cell use
<td>
with header cell semantics as often as they use<th>
. (Compared to 20% which use<th>
throughout.) - HTML4 (along with the books, tutorials, blogs and forum threads influenced by it) have been telling authors to apply header cell semantics to
<td>
in situations like the above for over a decade. <td headers>
pointing to<td id>
occurs in specialist tables.<th id>
works just as well, of course. But specialists can be very attached to existing practice and it is supported by some common ATs.<td scope="foo">
is sometimes used to avoid the centered bold styling of<th>
. It’s a lousy practice but at least it would remain accessible.- Increasing the proportion of fully accessible data tables from about 20% to about 40% at the cost of changing n lines in the HTML5 specification seems far more viable than retrofitting
<th>
to the n million documents currently using<td>
with header association markup. (Estimated from the proportions in my research and my understanding of how HTML5 would change.) - For header cell semantics to be there and for us not to support them is a terrible waste. (It’s rare for authors to even try making things accessible.)
- Support Existing Content.
- The HTML5 authoring guide can recommend authors use
<th>
and explain why this is better.
Equally Spanned Header Cells, Adjacent
Ignore the sizes of adjacent header cells until you break out of header cells and into data cells.
- Tables with multiple levels of column headers usually have the widest header cells in the upper levels. But sometimes the lower levels are the same width or wider. All the column header cells spanning that column are expected to apply.
- Tables with multiple levels of row headers may only have 1 row in some sections. Each row header cell is still expected to associate across that row.
- Tables with multiple levels of row header cells sometimes repeat the 1st level row header cell for each row it applies to.
Equally Spanned Header Cells, Distant
Block the current header cell from associating any further along that axis if you find a header cell with the same span after one or more data cells.
- Tables which repeat their column header cells every n rows expect the next set of repeated header cells to block the current set of header cells from going further since they share the same spans. Multiple levels of header cells may be repeated.
- A table has one or more levels of column headers and the table is also split into several sections. Each section starts by using one (or, occassionally, more) header cells which span the entire width of the table. Each section header cell is expected to combine with the column header cells because they have different spans. But each section header cell expects the next section header cell to block it from going further since they have the same span.
- (HTML5 already does this. It should remain even if the changes are made to Equally Spanned Header Cells, Adjacent.)
Equally Spanned Header Cells Using scope
, Distant
Should have the same blocking logic as the auto state.
- FTSE 100 Listings has 2 levels of column header cells. Both levels are wider than the lower level. They all use
<th scope="col">
, some withcolspan
as well. Each repeated header cell should block the earlier instances of it to avoid duplication, just as plain<th>
would.
Emptiness of Cells
Define emptiness the same way for header cells and data cells.
- Currently, HTML5 only defined emptiness of data cells.
Empty Data Cells
These must get header cells associated with them.
- Removing empty data cells is unclear. I figure it means UAs are not required to make associations with empty data cells.
- When navigating a table non-visually, orientation is difficult. Being able the query the current cell for all its header cells helps orientation, even when the user is on an empty cell.
- Tables can have many adjacent empty cells. If no associations are given to them, there will be no announcements in ATs as users move between them. Users will get nothing if they query an empty cell for its header cells. This will be disorienting.
Empty Headers Cells
These should not create associations since it’s effectively a no-op.
- Typically, an empty header cell is there to fill a slot. It is not expected to make associations.
- Associating an empty header cell with the current cell adds no orienting information to the current cell.
- An AT might reasonably provide a count for the number of header cells associated with the current cell. If one or more header cells are empty, a user will receive fewer pieces of header text than they expected. Preventing empty header cells from making associations makes the UI more straightforwards.
Heuristics
Early on, I considered <td><b>
and <td><strong>
as aliases of <th>
. I now think the algorithm should not use heuristics due to their unreliability in real data tables.
- Although
<td><b>
is clearly used as an alias for<th>
it is used for other things, too. Making<td><b>
an alias for<th>
would improve about 16% of the tables I surveyed in 2007. - However, it would break other uses in reasonable tables.
<td><strong>
for important cells or<td><b>
for every data cell in an important row would create false associations with subsequent cells. - Adding extra heuristics to handle such cases opens a can of worms.
Wide Header Cell Heuristic
Do not make a header cell at the start of a row with empty data cells act as if it spanned all those empty data cells.
- It does fit existing table arrangements where an author has not spanned a section header across the whole width of a table.
- However, it is risky because data may be unavailable. Often a placeholder character is used but these can just as easily be empty.
- Calendars exist where each row is one day but some days have no events. If the row header uses
<th>
, that whole row becomes a wide header and creates false associations with subsequent cells.
scope
, colspan
& rowspan
(15th November 2008)
In HTML4, scope
is unaffected by colspan
and rowspan
. Let me walk you through the spec lawyering which leads me to this conclusion.
Table of Contents
RTFM
HTML4 defines 4 modes for scope
:
row
:- The current cell provides header information for the rest of the row that contains it (see also the section on table directionality).
col
:- The current cell provides header information for the rest of the column that contains it.
rowgroup
:- The header cell provides header information for the rest of the row group that contains it.
colgroup
:- The header cell provides header information for the rest of the column group that contains it.
Already we see that scope
only applies to:
- “the rest of the row”
- “the rest of the column”
- “the rest of the row group”
- “the rest of the column group”
Special treatment of header cells using colspan
or rowspan
is conspicous by its absence.
scope="row"
Limits the association to “the rest of the row that contains it”. While talking about row groups:
Each row group must contain at least one row, defined by the
TR
element.
So a row corresponds to a <tr>
element. The header cell is only contained by the first <tr>
.
There is no prose to say a cell with rowspan
creates a special row. So “the row” doesn’t gain special meaning for spanned header cells.
Therefore, scope="row"
only applies the header cell to the first row it spans.
scope="col"
Limits the association to “the rest of the column that contains it”. However, what exactly is a column in HTML4 data tables? This is a surprisingly slippery question to answer.
Column ≠ <col>
!
Bit of a red herring but worth mentioning:
The
COL
does not group columns together structurally [...].
<col span>
means a<col>
element may span several columns.- The presence of either
<col>
or<col span>
does not affectscope="col"
. - The
<col>
element is optional, so<col>
and<col span>
are rarely found in data tables.
Number of Columns
Calculating the number of columns in a table specifies what to do in the absence of <col>
elements:
The number of columns is equal to the number of columns required by the row with the most columns, including cells that span multiple columns.
A cell with colspan
simply spans many columns. So “the column” doesn’t gain special meaning for header cells using colspan
.
It goes on to say:
For any row that has fewer than this number of columns, the end of that row should be padded with empty cells.
It seems one cell adds one column, although this is described rather fuzzily.
Contained by a Column
Cells cannot be a descendant of columnar markup in HTML4. So “the column” must take its meaning from “Calculating the number of columns in a table”. Namely, that one cell adds one column.
When a cell uses colspan
, the additional cells it spans across are counted as additional columns in “Calculating the number of columns in a table”. Given that only “the column that contains it” is affected by scope="col"
, that must only be the first column.
Therefore, scope="col"
only applies the header cell down the first column it spans.
scope="rowgroup"
Extends the association across “the rest of the row group that contains it.” From row groups:
When present, each
THEAD
,TFOOT
, andTBODY
contains a row group.
It also tell us a table without explicit row groups has an implied <tbody>
:
<!ELEMENT TBODY O O (TR)+ -- table body -->
[...]
The
TBODY
start tag is always required except when the table contains only one table body and no table head or foot sections.
Therefore, scope="rowgroup"
applies the header cell to an area:
- downwards from the first row it spans to the last row contained by the
<thead>
,<tfoot>
or<tbody>
which contains it; - and rightwards from the first column it spans in each of those rows across the rest of the table.
Using scope="rowgroup"
in a table with an implied <tbody>
(or in an explicit <thead>
, <tfoot>
or <tbody>
which includes the rest of the rows in that table) applies that cell to the rest of the table, even when the rowspan
finishes before that.
scope="colgroup"
Extends the association across “the rest of the column group that contains it.” From column groups:
Column groups allow authors to create structural divisions within a table. [...] The
COL
element allows authors to share attributes among several columns without implying any structural grouping.
So the <colgroup>
element is all that scope="colgroup"
is affected by. But how many columns does it cover? The specification for <colgroup span>
explains:
This attribute, which must be an integer > 0, specifies the number of columns in a column group. Values mean the following:
- In the absence of a
span
attribute, eachCOLGROUP
defines a column group containing one column.- If the
span
attribute is set to N > 0, the currentCOLGROUP
element defines a column group containing N columns.User agents must ignore this attribute if the
COLGROUP
element contains one or moreCOL
elements.
Calculating the number of columns in a table tells us that <col>
items inside a <colgroup>
do affect the number of columns that <colgroup>
spans:
- For each
COL
element, take the value of itsspan
attribute (default value 1).- For each
COLGROUP
element containing at least oneCOL
element, ignore the span attribute for theCOLGROUP
element. For eachCOL
element, perform the calculation of step 1.
(“Step 1” is actually the parent item of that list, so this doesn’t make sense. Treating the first item of this bulleted list as “step 1” makes sense, though.)
You need <colgroup>
elements “to create structural divisions within a table” and <colgroup span>
can do this by itself. But if <col>
or <col span>
are also present, they set the number of columns spanned by their <colgroup>
elements. These elements work in tandem.
Column groups tells us that a <colgroup>
is always present:
A table may either contain a single implicit column group (no
COLGROUP
element delimits the columns) or any number of explicit column groups (each delimited by an instance of theCOLGROUP
element).
Therefore scope="colgroup"
applies the header cell to an area:
- rightwards from the first column it spans to the last column spanned by the corresponding
<colgroup>
; - and downwards from the first row it spans to the end of table.
Using scope="colgroup"
in a table with an implied <colgroup>
(or an explicit <colgroup>
which includes the rest of the columns in that table) applies that header cell to the rest of the table, even when the colspan
finishes before that.
Innovation
Yet tables I find on the web expect spanned cells to work with these values. Plain <th>
spanning cells is also expected to Just Work. Tutorials I’ve seen on the subject advise this, too.
HTML5’s header association algorithm follows a research-oriented design process. So did HTML4’s, of course. But HTML5 has the luxury of studying how tables ended up being authored on the web at large. This lets it optimise the common cases while making it more robust.
HTML5 makes the common cases fully accessible with trivial markup: use <th>
for header cells and <td>
for data cells.
Conclusion
Eventually, tutorials should advise plain <th>
for header association in the common cases.
7th Blood Donation (14th November 2008)
Same location and use of my jumper too keep warm as before. Donation went really fast, although taking the needle out was pretty painful this time. It remained sore for an hour or so afterwards.
All part of being a hero, I guess! c{8¬)
TPAC Cost (11th November 2008)
Looks like the trip cost me getting on for £600 overall. Well worth spending an hour on the Excel expense sheet Mozilla sent me, with half a dozen receipts scanned and attached.
- Breakfast was €7 per morning at my hotel.
- Lunch was a free buffet at the venue.
- Dinners were between €15 and €20 as I rarely had a dessert.
On the TGV I had a surprisingly tasty and filling lunch. A long baguette with cheese, ham and lettuce followed by a small but very chocolatey dessert and washed down with hot chocolate. This came to under €10 but I had it both ways, so that’s €20 all told.
Not really worth claiming, I thought. But that’s around £15 which is twice what I make per hour as Calthorpe Park School’s part-time webmaster. Scanning in the receipt and making a sensibly sized PNG out of it was a cinch. Well, it certainly took less than 2 hours!
Leaving Accessify Forums (7th November 2008)
After spliting 78 threads individually to get the spam out, I’m leaving Accessify Forum.
Deleting a user and all their messages from the Admin Control Panel probably takes 10 clicks. There have been many requests behind the scenes to give moderators access to it. Somehow, the owner of the parent site has consistently refused to do this.
It boils down to an absentee owner who won’t let other people help.
— Ben Millard
It was just like the slow, grinding halt which made me leave MISA. There’s an entry from Fantasai which describes the feeling well.
Calthorpe Wins “Most Accessible Website” (4th November 2008)
We were announced as being in the final 10 of the Hantsweb Awards way back on 15th September 2008. Some time during October we were listed as finalists.
On 4th November 2008, at the awards ceremony, we were finally announced as the winners of the Most Accessible Website category.
The Awards Ceremony
Just like a miniature TV awards ceremony, there’s a stage in front of an audience with professional lighting, sound a local radio DJ as the host.
The venue was inside a planetarium. A massive digital screen forms a hemisphere above the audience.
Before proceedings really began, they ran an amazing CGI trailer type thing celebrating space exploration. Quite unexpected and very well produced! The moving camera angles coupled with the screen filling my peripheral vision made for a real sense of motion, even though we were all seated in very comfortable theatre chairs.
Some local politicians opened proceedings and the awards got underway promptly.
Our Chance in the Spotlight
“Most Accessible” was the first category. The big screen showed the award names and finalist websites as the host announced the category. All the finalists were invited to a set of chairs beside the stage so the audience could see us when the winner was announced.
It was Calthorpe Park School. We’d done it!
We went onto the stage and stood on our marks to get a picture taken. The audience were all very willing to applaud. It’s quite a nerve-racking experience, being the centre of attention like that. I was on a high but didn’t want to look insane for the photo.
While we were on stage, the host was saying how the judges summarised our website. I was so busy trying not to trip over, making sure I stood in the right place and was looking squarely into the camera lense that I didn’t hear a word of it!
We applauded the runners up, who were announced after we sat back down.
Other Finalists
The categories continued for an hour or so.
A lot of visibly well-designed websites became finalists. Mainly the winners had kept things simple. A couple stood out for marrying wonderfully clear layouts with great finesse and subtley in their graphics and palette. That’s what I’ve wanted for Calthorpe. Alas, I haven’t the skill and they haven’t the budget.
Warren and I occassionally noted the hyperlink styles websites use. I think he still dislikes the “blue and underlined” mantra I enforce on Calthorpe…
Success at Mingling
With the last prizes awarded, everyone headed for the buffet area. I had a handful of crisps and 2 sausage rolls, washed down with glasses of pleasant orange juice.
People seemed to be standing in the same groups rather than diffusing throughout the room. I had a go at finding some other school type places and chatted to Wyvern Technology College for a while. They use Joomla under the hood, as I’ve now confirmed:
<meta name="Generator" content="Joomla! - Copyright (C) 2005 - 2007 Open Source Matters. All rights reserved." />
Joomla is used by sDesign1 as well, such as Tower College. I’ve just notice the devastating news they’ll be moving to a Sharepoint-powered portal. There goes the neighbourhood. :*(
Dude who does the Wyvern Technology College site said it took a bit of customising to get it all to W3C standards. He knew about CSS layout and did cross-browser compatibility firefighting. Studied web design at university, although it was mostly graphic design.
We’re both hosted by Hampshire County Council and are both dead impressed by their service. They keep hitting the 150MB disk space limit. I suggested paying to get more. Not sure how much Calthorpe uses but we’ve not had issues with disk space, bandwidth or CPU usage.
Future Plans
At some point, I want Calthorpe to look as good as it works. I mentioned the requirements the current “design” has to accomodate to a Hampshire County Council communications dude, who interviewed us.
Our audience ranges from Year 5 kids all the way up to grandparents of Year 11 students and all points in between. It has to be many things at once:
- Fun enough for the young kids to be excited by it, or at least interested.
- Grown-up enough for the teenages to feel it isn’t “babyish”.
- Professional enough for parents to feel confident about the information it provides.
- Simple enough for the silver surfers.
Being the archetype for “accessibile but boring” is kinda lame, especially considering what I do with sDesign1. It’s a challenge worthy of a great designer. But great designers cost great piles of money which Calthorpe doesn’t have. Maybe we’ll find one next year.
Still, it’s a labour of love and I’m proud to win against 100 other entrants for 2 years in a row!